Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
- name: Telemetry destination
package: ./internal/resources/telemetrydestination
project_id_required: false
- name: Audit log export destination
package: ./internal/resources/auditlogexportdestination
project_id_required: false
audit_export_required: true
- name: Project data source
package: ./internal/datasources/project
project_id_required: true
Expand Down Expand Up @@ -64,6 +68,17 @@ jobs:
run: |
: "${KERNEL_PROJECT_ID:?Set the KERNEL_PROJECT_ID repository secret before running browser-pool acceptance tests.}"

- name: Check audit log export secrets
if: matrix.audit_export_required
env:
KERNEL_AUDIT_EXPORT_BUCKET: ${{ secrets.KERNEL_AUDIT_EXPORT_BUCKET }}
KERNEL_AUDIT_EXPORT_REGION: ${{ secrets.KERNEL_AUDIT_EXPORT_REGION }}
KERNEL_AUDIT_EXPORT_ROLE_ARN: ${{ secrets.KERNEL_AUDIT_EXPORT_ROLE_ARN }}
run: |
: "${KERNEL_AUDIT_EXPORT_BUCKET:?Set the KERNEL_AUDIT_EXPORT_BUCKET repository secret before running audit log export acceptance tests.}"
: "${KERNEL_AUDIT_EXPORT_REGION:?Set the KERNEL_AUDIT_EXPORT_REGION repository secret before running audit log export acceptance tests.}"
: "${KERNEL_AUDIT_EXPORT_ROLE_ARN:?Set the KERNEL_AUDIT_EXPORT_ROLE_ARN repository secret before running audit log export acceptance tests.}"

- name: Checkout
uses: actions/checkout@v6

Expand All @@ -85,6 +100,9 @@ jobs:
KERNEL_BASE_URL: ${{ secrets.KERNEL_BASE_URL }}
KERNEL_PROJECT_ID: ${{ matrix.project_id_required && secrets.KERNEL_PROJECT_ID || '' }}
KERNEL_ALT_PROJECT_ID: ${{ matrix.project_id_required && secrets.KERNEL_ALT_PROJECT_ID || '' }}
KERNEL_AUDIT_EXPORT_BUCKET: ${{ matrix.audit_export_required && secrets.KERNEL_AUDIT_EXPORT_BUCKET || '' }}
KERNEL_AUDIT_EXPORT_REGION: ${{ matrix.audit_export_required && secrets.KERNEL_AUDIT_EXPORT_REGION || '' }}
KERNEL_AUDIT_EXPORT_ROLE_ARN: ${{ matrix.audit_export_required && secrets.KERNEL_AUDIT_EXPORT_ROLE_ARN || '' }}
run: |
if [ -z "$KERNEL_BASE_URL" ]; then
unset KERNEL_BASE_URL
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Resources:
- `kernel_browser_pool`
- `kernel_project`
- `kernel_telemetry_destination`
- `kernel_audit_log_export_destination`

Data sources:

Expand All @@ -30,6 +31,7 @@ Import:
- `kernel_browser_pool` by canonical browser pool ID
- `kernel_project` by canonical project ID
- `kernel_telemetry_destination` by canonical telemetry destination ID
- `kernel_audit_log_export_destination` by canonical audit log export destination ID

## Not Supported

Expand All @@ -39,6 +41,7 @@ The provider intentionally does not manage:
- acquire, release, or flush
- app invocation
- logs, screenshots, or live view
- audit log search, download, or destination test deliveries
- runtime status or standby state
- force-release or recovery operations
- API key, profile, proxy, or extension resources
Expand Down Expand Up @@ -87,6 +90,7 @@ terraform import kernel_browser_pool.example <browser-pool-id>
terraform import kernel_browser_pool.example <project-id>/<browser-pool-id>
terraform import kernel_project.example <project-id>
terraform import kernel_telemetry_destination.example <telemetry-destination-id>
terraform import kernel_audit_log_export_destination.example <audit-log-export-destination-id>
```

Kernel never returns telemetry destination header values, so an imported
Expand Down
15 changes: 13 additions & 2 deletions docs/acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document is the live-API release gate for the provider's selected public
surface. Unit tests remain the fast default. Acceptance tests run after changes
reach `main`, through explicit local opt-in, or by manual workflow dispatch.

The selected surface contains three managed resources and four read-only data
The selected surface contains four managed resources and four read-only data
sources. It does not claim coverage for future or unregistered Kernel objects.

## Gate Rules
Expand All @@ -29,20 +29,30 @@ export KERNEL_API_KEY=...
export KERNEL_PROJECT_ID=...
export KERNEL_ALT_PROJECT_ID=... # optional second project
export KERNEL_BASE_URL=... # optional non-production API
export KERNEL_AUDIT_EXPORT_BUCKET=... # audit log export destination only
export KERNEL_AUDIT_EXPORT_REGION=...
export KERNEL_AUDIT_EXPORT_ROLE_ARN=...
```

`KERNEL_PROJECT_ID` is required for the browser-pool resource and all four data
sources. The project and telemetry destination resources are organization-scoped
and do not require it; the telemetry destination test needs an API key that is
not bound to a project.

The audit log export destination resource is organization-scoped too. Its test
needs an API key that is not bound to a project, on a Kernel plan that includes
audit logs, plus an S3 bucket, its region, and a role that Kernel can assume. The
test skips locally when those three variables are unset; the workflow fails
instead.

## Matrix

| Surface | Package | Live scenario |
| --- | --- | --- |
| `kernel_project` resource | `./internal/resources/project` | Create, rename with stable ID, no-drift plan, canonical-ID import, post-import no drift, delete, and HTTP 404 verification. |
| `kernel_browser_pool` resource | `./internal/resources/browserpool` | Create, durable update with stable ID, opt into rebuilding and acquire an idle browser after a configuration change, no-drift plan, provider-default and explicit project scope, bare and project-qualified import, non-force delete, and HTTP 404 verification. |
| `kernel_telemetry_destination` resource | `./internal/resources/telemetrydestination` | Create with headers, update name, endpoint, description, and headers with stable ID, remove all headers, verify header names through the API, no-drift plan, canonical-ID import, delete, and HTTP 404 verification. |
| `kernel_audit_log_export_destination` resource | `./internal/resources/auditlogexportdestination` | Create paused with a unique prefix, verify computed `external_id` and `kernel_role_arn`, activate explicitly, update the prefix and pause with stable ID, verify the paused status through the API, no-drift plan, canonical-ID import, delete, and HTTP 404 verification. |
| `kernel_project` data source | `./internal/datasources/project` | Create a unique project fixture, read it by ID and exact name, read the provider-default project, verify durable metadata and no drift, then delete and require coded `not_found`. |
| `kernel_profile` data source | `./internal/datasources/profile` | Create a durable profile fixture through the SDK, read it by ID and exact name with explicit and default project scope, verify durable metadata and no drift, then delete and require coded `not_found`. |
| `kernel_proxy` data source | `./internal/datasources/proxy` | Create a managed datacenter proxy fixture through the SDK, read it by ID and exact name with explicit and default project scope, verify durable masked metadata and no drift, then delete and require coded `not_found`. |
Expand All @@ -65,13 +75,14 @@ Run packages independently for fast failure isolation:
go test -count=1 -timeout=30m -v ./internal/resources/project -run TestAcc
go test -count=1 -timeout=30m -v ./internal/resources/browserpool -run TestAcc
go test -count=1 -timeout=30m -v ./internal/resources/telemetrydestination -run TestAcc
go test -count=1 -timeout=30m -v ./internal/resources/auditlogexportdestination -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/project -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/profile -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/proxy -run TestAcc
go test -count=1 -timeout=30m -v ./internal/datasources/extension -run TestAcc
```

The `Acceptance` workflow runs the same seven packages as separate matrix jobs
The `Acceptance` workflow runs the same eight packages as separate matrix jobs
with `fail-fast: false` after changes reach `main` and on manual dispatch.

## Outside The Selected Surface
Expand Down
3 changes: 2 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ These are intentionally not Terraform resources or actions:
- logs, screenshots, live view, or telemetry streams
- runtime counters, standby state, lease state, or session state
- force-release, recovery, or managed runtime browser updates
- billing, organization membership, audit-log, managed-auth, or internal administration resources
- billing, organization membership, managed-auth, or internal administration resources
- audit-log search, download, or destination test deliveries; `kernel_audit_log_export_destination` manages only the durable export configuration
- standalone secrets without a dedicated durable secrets API

`force_destroy` for browser pools is deferred beyond the initial v1 scope unless a demonstrated workflow justifies a later architecture amendment. Terraform should not terminate leased runtime work as ordinary durable-resource cleanup.
Expand Down
4 changes: 2 additions & 2 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ Use this checklist before publishing a Kernel Terraform provider version.
checks each archive, and verifies checksum coverage. A snapshot skips signing
and is not a publishable release.
- Run the complete [selected-surface acceptance matrix](acceptance.md) with real credentials against the release commit.
- The `Acceptance` workflow runs all seven packages as independent matrix jobs after changes reach `main` and supports manual dispatch. Keep live tests out of pull-request CI.
- The `Acceptance` workflow runs all eight packages as independent matrix jobs after changes reach `main` and supports manual dispatch. Keep live tests out of pull-request CI.
- Process-level timeouts can bypass Go test cleanup. After an interrupted or hard-timeout run:
1. In the Kernel dashboard or durable API, find projects, browser pools, profiles, proxies, extensions, and telemetry destinations named `kernel-tf-*` that were created during the failed workflow run.
1. In the Kernel dashboard or durable API, find projects, browser pools, profiles, proxies, extensions, and telemetry destinations named `kernel-tf-*`, and audit log export destinations whose prefix starts with `kernel-tf-`, that were created during the failed workflow run.
2. Delete leaked browser pools with `force=false`. If deletion conflicts with a lease, wait for the lease to end; do not force-release or recover the browser from Terraform cleanup.
3. Delete other leaked project-scoped fixtures, then delete a leaked project only after its child resources are gone and the organization still has another active project.
4. Read each canonical ID and require the expected not-found response before considering cleanup complete.
Expand Down
46 changes: 46 additions & 0 deletions docs/resources/audit_log_export_destination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "kernel_audit_log_export_destination Resource - Kernel"
subcategory: ""
description: |-
Kernel audit-log export destination. Kernel continuously delivers the organization's audit logs to an S3 bucket: its role, kernel_role_arn, assumes your role_arn with external_id as the sts:ExternalId condition. Destinations are organization-scoped, require an organization-scoped API key, and require a Kernel plan that includes audit logs. New destinations are paused. Create the destination, configure the role's trust policy using the computed values, verify destination access, then set status = "active" and apply again. Delivery starts when the destination is activated; events recorded before activation or while paused are never exported. Replacing a destination issues a new external_id, so the role's trust policy has to be updated to match.
---

# kernel_audit_log_export_destination (Resource)

Kernel audit-log export destination. Kernel continuously delivers the organization's audit logs to an S3 bucket: its role, `kernel_role_arn`, assumes your `role_arn` with `external_id` as the `sts:ExternalId` condition. Destinations are organization-scoped, require an organization-scoped API key, and require a Kernel plan that includes audit logs. New destinations are paused. Create the destination, configure the role's trust policy using the computed values, verify destination access, then set `status = "active"` and apply again. Delivery starts when the destination is activated; events recorded before activation or while paused are never exported. Replacing a destination issues a new `external_id`, so the role's trust policy has to be updated to match.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `bucket` (String) S3 bucket name.
- `prefix` (String) Key prefix for exported objects.
- `region` (String) AWS region of the bucket.
- `role_arn` (String) ARN of the IAM role in your account that Kernel assumes to write objects.

### Optional

- `format` (String) Object format. Only `jsonl.gz` is supported. Changing it replaces the destination.
- `kms_key_id` (String) KMS key ID, alias, or ARN used to encrypt exported objects. Removing it clears the key.
- `status` (String) `active` or `paused`. Pausing stops delivery, and resuming starts again from the time of the resume. When omitted, Terraform does not manage the status and new destinations remain paused.
- `type` (String) Destination type. Only `s3` is supported. Changing it replaces the destination.

### Read-Only

- `external_id` (String) Value Kernel sends as `sts:ExternalId` when it assumes `role_arn`. Require it in the role's trust policy.
- `id` (String) Unique audit-log export destination identifier.
- `kernel_role_arn` (String) Kernel role that assumes `role_arn`. Allow it as the principal in the role's trust policy.

## Import

Import is supported using the following syntax:

The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:

```shell
terraform import kernel_audit_log_export_destination.example <audit-log-export-destination-id>
```
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ override setup.
- [project](project) creates a durable Kernel project with an explicit unique name.
- [project-scoped-browser-pool](project-scoped-browser-pool) places a browser pool in an explicit project, overriding the provider-level `project_id` default.
- [telemetry-destination](telemetry-destination) creates an OTLP destination that browser sessions can export telemetry to.
- [audit-log-export-destination](audit-log-export-destination) exports the organization's audit logs to S3 through an IAM role that trusts Kernel.
51 changes: 51 additions & 0 deletions examples/audit-log-export-destination/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
terraform {
required_providers {
kernel = {
source = "kernel/kernel"
}
}
}

provider "kernel" {}

variable "bucket" {
type = string
description = "S3 bucket that receives the exported audit logs."
}

variable "region" {
type = string
description = "AWS region of the bucket."
}

variable "role_arn" {
type = string
description = "IAM role in your account that Kernel assumes to write to the bucket."
}

resource "kernel_audit_log_export_destination" "example" {
bucket = var.bucket
region = var.region
prefix = "kernel/audit-logs/"
role_arn = var.role_arn
# First apply paused, attach the trust policy below, and verify destination
# access. Then change this to "active" and apply again to start delivery.
status = "paused"
}

# Attach this trust policy to var.role_arn so Kernel can assume it.
output "trust_policy" {
value = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { AWS = kernel_audit_log_export_destination.example.kernel_role_arn }
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"sts:ExternalId" = kernel_audit_log_export_destination.example.external_id
}
}
}]
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import kernel_audit_log_export_destination.example <audit-log-export-destination-id>
40 changes: 40 additions & 0 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const (
EnvProjectID = "KERNEL_PROJECT_ID"
// EnvAltProjectID names a second project for cross-project override tests.
EnvAltProjectID = "KERNEL_ALT_PROJECT_ID"
// The audit-log export destination test needs an S3 bucket and a role
// that trusts Kernel; it skips when these are unset.
EnvAuditExportBucket = "KERNEL_AUDIT_EXPORT_BUCKET"
EnvAuditExportRegion = "KERNEL_AUDIT_EXPORT_REGION"
EnvAuditExportRoleARN = "KERNEL_AUDIT_EXPORT_ROLE_ARN"

cleanupTimeout = 2 * time.Minute
)
Expand Down Expand Up @@ -194,6 +199,41 @@ func cleanupTelemetryDestination(t testing.TB, client telemetryDestinationCleane
})
}

func CleanupAuditLogExportDestination(t testing.TB, id string) {
t.Helper()

cleanupAuditLogExportDestination(t, ClientFromEnv(), id)
}

type auditLogExportDestinationCleaner interface {
DeleteAuditLogExportDestination(context.Context, string) error
}

func cleanupAuditLogExportDestination(t testing.TB, client auditLogExportDestinationCleaner, id string) {
t.Helper()

if id == "" {
return
}
if !AcceptanceEnabled() {
t.Fatalf("%s must be set to clean up Kernel acceptance test resources", EnvAcceptance)
return
}
if os.Getenv(EnvAPIKey) == "" {
t.Fatalf("%s must be set to clean up Kernel acceptance test resources", EnvAPIKey)
return
}

t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
defer cancel()

if err := client.DeleteAuditLogExportDestination(ctx, id); err != nil && !IsNotFound(err) {
t.Errorf("cleanup Kernel audit log export destination %s: %v", id, err)
}
})
}

// CleanupProfile registers a cleanup that deletes the profile from projectID;
// empty means the env-configured default project.
func CleanupProfile(t testing.TB, projectID, id string) {
Expand Down
20 changes: 20 additions & 0 deletions internal/kernelclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Clients struct {
extensions kernel.ExtensionService
browserPools kernel.BrowserPoolService
telemetryDestinations kernel.TelemetryDestinationService
auditLogExports kernel.AuditLogExportDestinationService
}

type Option func(*clientOptions)
Expand Down Expand Up @@ -71,6 +72,7 @@ func New(config Config, opts ...Option) Clients {
extensions: kernel.NewExtensionService(requestOpts...),
browserPools: kernel.NewBrowserPoolService(requestOpts...),
telemetryDestinations: kernel.NewTelemetryDestinationService(requestOpts...),
auditLogExports: kernel.NewAuditLogExportDestinationService(requestOpts...),
}
}

Expand Down Expand Up @@ -116,6 +118,24 @@ func (c Clients) DeleteTelemetryDestination(ctx context.Context, id string) erro
return c.telemetryDestinations.Delete(ctx, id, noMutationRetries())
}

// Audit-log export destinations are org-scoped too.

func (c Clients) CreateAuditLogExportDestination(ctx context.Context, params kernel.AuditLogExportDestinationNewParams) (*kernel.AuditLogExportDestination, error) {
return c.auditLogExports.New(ctx, params, noMutationRetries())
}

func (c Clients) GetAuditLogExportDestination(ctx context.Context, id string) (*kernel.AuditLogExportDestination, error) {
return c.auditLogExports.Get(ctx, id)
}

func (c Clients) UpdateAuditLogExportDestination(ctx context.Context, id string, params kernel.AuditLogExportDestinationUpdateParams) (*kernel.AuditLogExportDestination, error) {
return c.auditLogExports.Update(ctx, id, params, noMutationRetries())
}

func (c Clients) DeleteAuditLogExportDestination(ctx context.Context, id string) error {
return c.auditLogExports.Delete(ctx, id, noMutationRetries())
}

// The remaining methods are project-scoped and take the resolved project
// for each call.

Expand Down
Loading