Skip to content

Commit

Permalink
v2/logging: add support for S3 logstreaming
Browse files Browse the repository at this point in the history
We recently added support for S3 logstreaming endpoints to our API. This
involved adding several new fields on the GET LogstreamConfiguration and
PUT LogstreamConfiguration endpoints (and a new destinationType, "s3"),
plus a new AWSExternalID resource and two new endpoints related to it.

This commit updates the Go client library to reflect all of these changes.

Updates tailscale/corp#24533

Signed-off-by: Zach Hauser <[email protected]>
  • Loading branch information
zehauser committed Jan 22, 2025
1 parent 8143c7d commit c84613b
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 12 deletions.
78 changes: 70 additions & 8 deletions v2/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,48 @@ const (
LogstreamCriblEndpoint LogstreamEndpointType = "cribl"
LogstreamDatadogEndpoint LogstreamEndpointType = "datadog"
LogstreamAxiomEndpoint LogstreamEndpointType = "axiom"
LogstreamS3Endpoint LogstreamEndpointType = "s3"
)

const (
LogTypeConfig LogType = "configuration"
LogTypeNetwork LogType = "network"
)

const (
S3AccessKeyAuthentication S3AuthenticationType = "accesskey"
S3RoleARNAuthentication S3AuthenticationType = "rolearn"
)

// LogstreamConfiguration type defines a log stream entity in tailscale.
type LogstreamConfiguration struct {
LogType LogType `json:"logType,omitempty"`
DestinationType LogstreamEndpointType `json:"destinationType,omitempty"`
URL string `json:"url,omitempty"`
User string `json:"user,omitempty"`
LogType LogType `json:"logType,omitempty"`
DestinationType LogstreamEndpointType `json:"destinationType,omitempty"`
URL string `json:"url,omitempty"`
User string `json:"user,omitempty"`
S3Bucket string `json:"s3Bucket,omitempty"`
S3Region string `json:"s3Region,omitempty"`
S3KeyPrefix string `json:"s3KeyPrefix,omitempty"`
S3AuthenticationType S3AuthenticationType `json:"s3AuthenticationType,omitempty"`
S3AccessKeyID string `json:"s3AccessKeyId,omitempty"`
S3RoleARN string `json:"s3RoleArn,omitempty"`
S3ExternalID string `json:"s3ExternalId,omitempty"`
}

// SetLogstreamConfigurationRequest type defines a request for setting a LogstreamConfiguration.
type SetLogstreamConfigurationRequest struct {
DestinationType LogstreamEndpointType `json:"destinationType,omitempty"`
URL string `json:"url,omitempty"`
User string `json:"user,omitempty"`
Token string `json:"token,omitempty"`
DestinationType LogstreamEndpointType `json:"destinationType,omitempty"`
URL string `json:"url,omitempty"`
User string `json:"user,omitempty"`
Token string `json:"token,omitempty"`
S3Bucket string `json:"s3Bucket,omitempty"`
S3Region string `json:"s3Region,omitempty"`
S3KeyPrefix string `json:"s3KeyPrefix,omitempty"`
S3AuthenticationType S3AuthenticationType `json:"s3AuthenticationType,omitempty"`
S3AccessKeyID string `json:"s3AccessKeyId,omitempty"`
S3SecretAccessKey string `json:"s3SecretAccessKey,omitempty"`
S3RoleARN string `json:"s3RoleArn,omitempty"`
S3ExternalID string `json:"s3ExternalId,omitempty"`
}

// LogstreamEndpointType describes the type of the endpoint.
Expand All @@ -49,6 +70,9 @@ type LogstreamEndpointType string
// LogType describes the type of logging.
type LogType string

// S3AuthenticationType describes the type of authentication used to stream logs to a LogstreamS3Endpoint.
type S3AuthenticationType string

// LogstreamConfiguration retrieves the tailnet's [LogstreamConfiguration] for the given [LogType].
func (lr *LoggingResource) LogstreamConfiguration(ctx context.Context, logType LogType) (*LogstreamConfiguration, error) {
req, err := lr.buildRequest(ctx, http.MethodGet, lr.buildTailnetURL("logging", logType, "stream"))
Expand Down Expand Up @@ -78,3 +102,41 @@ func (lr *LoggingResource) DeleteLogstreamConfiguration(ctx context.Context, log

return lr.do(req, nil)
}

// AWSExternalID represents an AWS External ID that Tailscale can use to stream logs from a
// particular Tailscale AWS account to a LogstreamS3Endpoint that uses S3RoleARNAuthentication.
type AWSExternalID struct {
ExternalID string `json:"externalId,omitempty"`
TailscaleAWSAccountID string `json:"tailscaleAwsAccountId,omitempty"`
}

// CreateOrGetAwsExternalIdRequest is a request to create/get an AWS External ID from Tailscale.
type CreateOrGetAwsExternalIdRequest struct {
Reusable bool `json:"reusable,omitempty"`
}

// CreateOrGetAwsExternalId gets an AWS External ID that Tailscale can use to stream logs to
// a LogstreamS3Endpoint using S3RoleARNAuthentication, creating a new one for this tailnet
// when necessary.
func (lr *LoggingResource) CreateOrGetAwsExternalId(ctx context.Context, request CreateOrGetAwsExternalIdRequest) (*AWSExternalID, error) {
req, err := lr.buildRequest(ctx, http.MethodPost, lr.buildTailnetURL("aws-external-id"), requestBody(request))
if err != nil {
return nil, err
}
return body[AWSExternalID](lr, req)
}

// ValidateAWSTrustPolicyRequest is a request to validate that Tailscale can assume your AWS IAM role.
type ValidateAWSTrustPolicyRequest struct {
RoleARN string `json:"roleArn,omitempty"`
}

// ValidateAWSTrustPolicy validates that Tailscale can assume your AWS IAM role with (and only
// with) the given AWS External ID.
func (lr *LoggingResource) ValidateAWSTrustPolicy(ctx context.Context, awsExternalID string, request ValidateAWSTrustPolicyRequest) error {
req, err := lr.buildRequest(ctx, http.MethodPost, lr.buildTailnetURL("aws-external-id", awsExternalID, "validate-aws-trust-policy"), requestBody(request))
if err != nil {
return err
}
return lr.do(req, nil)
}
59 changes: 55 additions & 4 deletions v2/logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,18 @@ func TestClient_SetLogstreamConfiguration(t *testing.T) {
server.ResponseCode = http.StatusOK

logstreamRequest := tsclient.SetLogstreamConfigurationRequest{
DestinationType: tsclient.LogstreamCriblEndpoint,
URL: "http://example.com",
User: "my-user",
Token: "my-token",
DestinationType: tsclient.LogstreamCriblEndpoint,
URL: "http://example.com",
User: "my-user",
Token: "my-token",
S3Bucket: "my-bucket",
S3Region: "us-west-2",
S3KeyPrefix: "logs/",
S3AuthenticationType: tsclient.S3AccessKeyAuthentication,
S3AccessKeyID: "my-access-key-id",
S3SecretAccessKey: "my-secret-access-key",
S3RoleARN: "my-role-arn",
S3ExternalID: "my-external-id",
}
server.ResponseBody = nil

Expand All @@ -64,3 +72,46 @@ func TestClient_DeleteLogstream(t *testing.T) {
assert.Equal(t, http.MethodDelete, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/logging/configuration/stream", server.Path)
}

func TestClient_CreateOrGetAwsExternalId(t *testing.T) {
t.Parallel()

client, server := NewTestHarness(t)
server.ResponseCode = http.StatusOK

expectedExternalID := &tsclient.AWSExternalID{
ExternalID: "external-id",
TailscaleAWSAccountID: "account-id",
}
server.ResponseBody = expectedExternalID

request := tsclient.CreateOrGetAwsExternalIdRequest{
Reusable: true,
}

actualExternalID, err := client.Logging().CreateOrGetAwsExternalId(context.Background(), request)
assert.NoError(t, err)
assert.Equal(t, http.MethodPost, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/aws-external-id", server.Path)
assert.Equal(t, expectedExternalID, actualExternalID)
}

func TestClient_ValidateAWSTrustPolicy(t *testing.T) {
t.Parallel()

client, server := NewTestHarness(t)
server.ResponseCode = http.StatusOK

request := tsclient.ValidateAWSTrustPolicyRequest{
RoleARN: "arn:aws:iam::123456789012:role/example-role",
}

err := client.Logging().ValidateAWSTrustPolicy(context.Background(), "external-id-0000-0000", request)
assert.NoError(t, err)
assert.Equal(t, http.MethodPost, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/aws-external-id/external-id-0000-0000/validate-aws-trust-policy", server.Path)
var receivedRequest tsclient.ValidateAWSTrustPolicyRequest
err = json.Unmarshal(server.Body.Bytes(), &receivedRequest)
assert.NoError(t, err)
assert.EqualValues(t, request, receivedRequest)
}

0 comments on commit c84613b

Please sign in to comment.