diff --git a/changes/issue-18898-external-id b/changes/issue-18898-external-id new file mode 100644 index 0000000000..47a310be06 --- /dev/null +++ b/changes/issue-18898-external-id @@ -0,0 +1 @@ +Add support for ExternalId in STS Assume Role APIs diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index cd6c98244c..34d765c9b8 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -352,6 +352,7 @@ the way that the Fleet server works. AccessKeyID: config.Firehose.AccessKeyID, SecretAccessKey: config.Firehose.SecretAccessKey, StsAssumeRoleArn: config.Firehose.StsAssumeRoleArn, + StsExternalID: config.Firehose.StsExternalID, }, Kinesis: logging.KinesisConfig{ Region: config.Kinesis.Region, @@ -359,12 +360,14 @@ the way that the Fleet server works. AccessKeyID: config.Kinesis.AccessKeyID, SecretAccessKey: config.Kinesis.SecretAccessKey, StsAssumeRoleArn: config.Kinesis.StsAssumeRoleArn, + StsExternalID: config.Kinesis.StsExternalID, }, Lambda: logging.LambdaConfig{ Region: config.Lambda.Region, AccessKeyID: config.Lambda.AccessKeyID, SecretAccessKey: config.Lambda.SecretAccessKey, StsAssumeRoleArn: config.Lambda.StsAssumeRoleArn, + StsExternalID: config.Lambda.StsExternalID, }, PubSub: logging.PubSubConfig{ Project: config.PubSub.Project, diff --git a/docs/Configuration/fleet-server-configuration.md b/docs/Configuration/fleet-server-configuration.md index 0bb022f8c5..2714cce7b9 100644 --- a/docs/Configuration/fleet-server-configuration.md +++ b/docs/Configuration/fleet-server-configuration.md @@ -1430,7 +1430,7 @@ AWS secret access key to use for Firehose authentication. firehose: secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY ``` - +Optional unique identifier that can be used by the principal assuming the role to assert its identity. ##### firehose_sts_assume_role_arn This flag only has effect if one of the following is true: @@ -1447,6 +1447,23 @@ AWS STS role ARN to use for Firehose authentication. sts_assume_role_arn: arn:aws:iam::1234567890:role/firehose-role ``` +##### firehose_sts_external_id + +This flag only has effect if one of the following is true: +- `osquery_result_log_plugin` or `osquery_status_log_plugin` are set to `firehose`. +- `activity_audit_log_plugin` is set to `firehose` and `activity_enable_audit_log` is set to `true`. + +AWS STS External ID to use for Firehose authentication. This is typically used in +conjunction with an STS role ARN to ensure that only the intended AWS account can assume the role. + +- Default value: none +- Environment variable: `FLEET_FIREHOSE_STS_EXTERNAL_ID` +- Config file format: + ```yaml + firehose: + sts_external_id: your_unique_id + ``` + ##### firehose_status_stream This flag only has effect if `osquery_status_log_plugin` is set to `firehose`. @@ -1519,6 +1536,7 @@ firehose: access_key_id: AKIAIOSFODNN7EXAMPLE secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY sts_assume_role_arn: arn:aws:iam::1234567890:role/firehose-role + sts_external_id: your_unique_id status_stream: osquery_status result_stream: osquery_result ``` @@ -1594,6 +1612,23 @@ AWS STS role ARN to use for Kinesis authentication. sts_assume_role_arn: arn:aws:iam::1234567890:role/kinesis-role ``` +##### kinesis_sts_external_id + +This flag only has effect if one of the following is true: +- `osquery_result_log_plugin` or `osquery_status_log_plugin` are set to `kinesis`. +- `activity_audit_log_plugin` is set to `kinesis` and `activity_enable_audit_log` is set to `true`. + +AWS STS External ID to use for Kinesis authentication. This is typically used in +conjunction with an STS role ARN to ensure that only the intended AWS account can assume the role. + +- Default value: none +- Environment variable: `FLEET_KINESIS_STS_EXTERNAL_ID` +- Config file format: + ```yaml + kinesis: + sts_external_id: your_unique_id + ``` + ##### kinesis_status_stream This flag only has effect if `osquery_status_log_plugin` is set to `kinesis`. @@ -1665,6 +1700,7 @@ kinesis: access_key_id: AKIAIOSFODNN7EXAMPLE secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY sts_assume_role_arn: arn:aws:iam::1234567890:role/firehose-role + sts_external_id: your_unique_id status_stream: osquery_status result_stream: osquery_result ``` @@ -1740,6 +1776,23 @@ AWS STS role ARN to use for Lambda authentication. sts_assume_role_arn: arn:aws:iam::1234567890:role/lambda-role ``` +##### lambda_sts_external_id + +This flag only has effect if one of the following is true: +- `osquery_result_log_plugin` or `osquery_status_log_plugin` are set to `lambda`. +- `activity_audit_log_plugin` is set to `lambda` and `activity_enable_audit_log` is set to `true`. + +AWS STS External ID to use for Lambda authentication. This is typically used in +conjunction with an STS role ARN to ensure that only the intended AWS account can assume the role. + +- Default value: none +- Environment variable: `FLEET_LAMBDA_STS_EXTERNAL_ID` +- Config file format: + ```yaml + lambda: + sts_external_id: your_unique_id + ``` + ##### lambda_status_function This flag only has effect if `osquery_status_log_plugin` is set to `lambda`. @@ -1907,9 +1960,7 @@ pubsub: project: my-gcp-project result_topic: osquery_result status_topic: osquery_status - sts_assume_role_arn: arn:aws:iam::1234567890:role/firehose-role - status_function: statusFunction - result_function: resultFunction + add_attributes: true ``` #### Kafka REST Proxy logging @@ -2102,6 +2153,22 @@ AWS STS role ARN to use for SES authentication. sts_assume_role_arn: arn:aws:iam::1234567890:role/ses-role ``` +##### ses_sts_external_id + +This flag only has effect if `email.backend` or `FLEET_EMAIL_BACKEND` is set to `ses`. + +AWS STS External ID to use for SES authentication. This is typically used in +conjunction with an STS role ARN to ensure that only the intended AWS account can assume the role. + + +- Default value: none +- Environment variable: `FLEET_SES_STS_EXTERNAL_ID` +- Config file format: + ```yaml + ses: + sts_external_id: your_unique_id + ``` + ##### ses_source_arn This flag only has effect if `email.backend` or `FLEET_EMAIL_BACKEND` is set to `ses`. This configuration **is @@ -2187,6 +2254,19 @@ AWS STS role ARN to use for S3 authentication. sts_assume_role_arn: arn:aws:iam::1234567890:role/some-s3-role ``` +##### s3_sts_external_id + +AWS STS External ID to use for S3 authentication. This is typically used in +conjunction with an STS role ARN to ensure that only the intended AWS account can assume the role. + +- Default value: none +- Environment variable: `FLEET_S3_STS_EXTERNAL_ID` +- Config file format: + ```yaml + s3: + sts_external_id: your_unique_id + ``` + ##### s3_endpoint_url AWS S3 Endpoint URL. Override when using a different S3 compatible object storage backend (such as Minio), @@ -2622,6 +2702,20 @@ This is the AWS STS role ARN for S3 authentication. sts_assume_role_arn: arn:aws:iam::1234567890:role/some-s3-role ``` +##### packaging_s3_sts_external_id + +AWS STS External ID to use for S3 authentication. This is typically used in +conjunction with an STS role ARN to ensure that only the intended AWS account can assume the role. + +- Default value: "" +- Environment variable: `FLEET_PACKAGING_S3_STS_EXTERNAL_ID` +- Config file format: + ```yaml + packaging: + s3: + sts_external_id: your_unique_id + ``` + ##### packaging_s3_endpoint_url This is the AWS S3 Endpoint URL. Override when using a different S3 compatible object storage backend (such as Minio) diff --git a/server/config/config.go b/server/config/config.go index 7861729762..4f2c44be64 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -243,6 +243,7 @@ type FirehoseConfig struct { AccessKeyID string `yaml:"access_key_id"` SecretAccessKey string `yaml:"secret_access_key"` StsAssumeRoleArn string `yaml:"sts_assume_role_arn"` + StsExternalID string `yaml:"sts_external_id"` StatusStream string `yaml:"status_stream"` ResultStream string `yaml:"result_stream"` AuditStream string `yaml:"audit_stream"` @@ -255,6 +256,7 @@ type KinesisConfig struct { AccessKeyID string `yaml:"access_key_id"` SecretAccessKey string `yaml:"secret_access_key"` StsAssumeRoleArn string `yaml:"sts_assume_role_arn"` + StsExternalID string `yaml:"sts_external_id"` StatusStream string `yaml:"status_stream"` ResultStream string `yaml:"result_stream"` AuditStream string `yaml:"audit_stream"` @@ -267,6 +269,7 @@ type SESConfig struct { AccessKeyID string `yaml:"access_key_id"` SecretAccessKey string `yaml:"secret_access_key"` StsAssumeRoleArn string `yaml:"sts_assume_role_arn"` + StsExternalID string `yaml:"sts_external_id"` SourceArn string `yaml:"source_arn"` } @@ -280,6 +283,7 @@ type LambdaConfig struct { AccessKeyID string `yaml:"access_key_id"` SecretAccessKey string `yaml:"secret_access_key"` StsAssumeRoleArn string `yaml:"sts_assume_role_arn"` + StsExternalID string `yaml:"sts_external_id"` StatusFunction string `yaml:"status_function"` ResultFunction string `yaml:"result_function"` AuditFunction string `yaml:"audit_function"` @@ -294,6 +298,7 @@ type S3Config struct { AccessKeyID string `yaml:"access_key_id"` SecretAccessKey string `yaml:"secret_access_key"` StsAssumeRoleArn string `yaml:"sts_assume_role_arn"` + StsExternalID string `yaml:"sts_external_id"` DisableSSL bool `yaml:"disable_ssl"` ForceS3PathStyle bool `yaml:"force_s3_path_style"` } @@ -948,6 +953,7 @@ func (man Manager) addConfigs() { man.addConfigString("ses.access_key_id", "", "Access Key ID for AWS authentication") man.addConfigString("ses.secret_access_key", "", "Secret Access Key for AWS authentication") man.addConfigString("ses.sts_assume_role_arn", "", "ARN of role to assume for AWS") + man.addConfigString("ses.sts_external_id", "", "Optional unique identifier that can be used by the principal assuming the role to assert its identity.") man.addConfigString("ses.source_arn", "", "ARN of the identity that is associated with the sending authorization policy that permits you to send for the email address specified in the Source parameter") // Firehose @@ -958,6 +964,8 @@ func (man Manager) addConfigs() { man.addConfigString("firehose.secret_access_key", "", "Secret Access Key for AWS authentication") man.addConfigString("firehose.sts_assume_role_arn", "", "ARN of role to assume for AWS") + man.addConfigString("firehose.sts_external_id", "", + "Optional unique identifier that can be used by the principal assuming the role to assert its identity.") man.addConfigString("firehose.status_stream", "", "Firehose stream name for status logs") man.addConfigString("firehose.result_stream", "", @@ -973,6 +981,8 @@ func (man Manager) addConfigs() { man.addConfigString("kinesis.secret_access_key", "", "Secret Access Key for AWS authentication") man.addConfigString("kinesis.sts_assume_role_arn", "", "ARN of role to assume for AWS") + man.addConfigString("kinesis.sts_external_id", "", + "Optional unique identifier that can be used by the principal assuming the role to assert its identity.") man.addConfigString("kinesis.status_stream", "", "Kinesis stream name for status logs") man.addConfigString("kinesis.result_stream", "", @@ -986,6 +996,8 @@ func (man Manager) addConfigs() { man.addConfigString("lambda.secret_access_key", "", "Secret Access Key for AWS authentication") man.addConfigString("lambda.sts_assume_role_arn", "", "ARN of role to assume for AWS") + man.addConfigString("lambda.sts_external_id", "", + "Optional unique identifier that can be used by the principal assuming the role to assert its identity.") man.addConfigString("lambda.status_function", "", "Lambda function name for status logs") man.addConfigString("lambda.result_function", "", @@ -1001,6 +1013,7 @@ func (man Manager) addConfigs() { man.addConfigString("s3.access_key_id", "", "Access Key ID for AWS authentication") man.addConfigString("s3.secret_access_key", "", "Secret Access Key for AWS authentication") man.addConfigString("s3.sts_assume_role_arn", "", "ARN of role to assume for AWS") + man.addConfigString("s3.sts_external_id", "", "Optional unique identifier that can be used by the principal assuming the role to assert its identity.") man.addConfigBool("s3.disable_ssl", false, "Disable SSL (typically for local testing)") man.addConfigBool("s3.force_s3_path_style", false, "Set this to true to force path-style addressing, i.e., `http://s3.amazonaws.com/BUCKET/KEY`") @@ -1088,6 +1101,7 @@ func (man Manager) addConfigs() { man.addConfigString("packaging.s3.access_key_id", "", "Access Key ID for AWS authentication") man.addConfigString("packaging.s3.secret_access_key", "", "Secret Access Key for AWS authentication") man.addConfigString("packaging.s3.sts_assume_role_arn", "", "ARN of role to assume for AWS") + man.addConfigString("packaging.s3.sts_external_id", "", "Optional unique identifier that can be used by the principal assuming the role to assert its identity.") man.addConfigBool("packaging.s3.disable_ssl", false, "Disable SSL (typically for local testing)") man.addConfigBool("packaging.s3.force_s3_path_style", false, "Set this to true to force path-style addressing, i.e., `http://s3.amazonaws.com/BUCKET/KEY`") @@ -1253,6 +1267,7 @@ func (man Manager) LoadConfig() FleetConfig { AccessKeyID: man.getConfigString("firehose.access_key_id"), SecretAccessKey: man.getConfigString("firehose.secret_access_key"), StsAssumeRoleArn: man.getConfigString("firehose.sts_assume_role_arn"), + StsExternalID: man.getConfigString("firehose.sts_external_id"), StatusStream: man.getConfigString("firehose.status_stream"), ResultStream: man.getConfigString("firehose.result_stream"), AuditStream: man.getConfigString("firehose.audit_stream"), @@ -1266,6 +1281,7 @@ func (man Manager) LoadConfig() FleetConfig { ResultStream: man.getConfigString("kinesis.result_stream"), AuditStream: man.getConfigString("kinesis.audit_stream"), StsAssumeRoleArn: man.getConfigString("kinesis.sts_assume_role_arn"), + StsExternalID: man.getConfigString("kinesis.sts_external_id"), }, Lambda: LambdaConfig{ Region: man.getConfigString("lambda.region"), @@ -1275,6 +1291,7 @@ func (man Manager) LoadConfig() FleetConfig { ResultFunction: man.getConfigString("lambda.result_function"), AuditFunction: man.getConfigString("lambda.audit_function"), StsAssumeRoleArn: man.getConfigString("lambda.sts_assume_role_arn"), + StsExternalID: man.getConfigString("lambda.sts_external_id"), }, S3: S3Config{ Bucket: man.getConfigString("s3.bucket"), @@ -1284,6 +1301,7 @@ func (man Manager) LoadConfig() FleetConfig { AccessKeyID: man.getConfigString("s3.access_key_id"), SecretAccessKey: man.getConfigString("s3.secret_access_key"), StsAssumeRoleArn: man.getConfigString("s3.sts_assume_role_arn"), + StsExternalID: man.getConfigString("s3.sts_external_id"), DisableSSL: man.getConfigBool("s3.disable_ssl"), ForceS3PathStyle: man.getConfigBool("s3.force_s3_path_style"), }, @@ -1296,6 +1314,7 @@ func (man Manager) LoadConfig() FleetConfig { AccessKeyID: man.getConfigString("ses.access_key_id"), SecretAccessKey: man.getConfigString("ses.secret_access_key"), StsAssumeRoleArn: man.getConfigString("ses.sts_assume_role_arn"), + StsExternalID: man.getConfigString("ses.sts_external_id"), SourceArn: man.getConfigString("ses.source_arn"), }, PubSub: PubSubConfig{ @@ -1365,6 +1384,7 @@ func (man Manager) LoadConfig() FleetConfig { AccessKeyID: man.getConfigString("packaging.s3.access_key_id"), SecretAccessKey: man.getConfigString("packaging.s3.secret_access_key"), StsAssumeRoleArn: man.getConfigString("packaging.s3.sts_assume_role_arn"), + StsExternalID: man.getConfigString("packaging.s3.sts_external_id"), DisableSSL: man.getConfigBool("packaging.s3.disable_ssl"), ForceS3PathStyle: man.getConfigBool("packaging.s3.force_s3_path_style"), }, diff --git a/server/datastore/s3/s3.go b/server/datastore/s3/s3.go index 5f709e29cf..ebd0e0c3a9 100644 --- a/server/datastore/s3/s3.go +++ b/server/datastore/s3/s3.go @@ -49,8 +49,11 @@ func newS3store(config config.S3Config) (*s3store, error) { // Assume role if configured if config.StsAssumeRoleArn != "" { - stscreds.NewCredentials(sess, config.StsAssumeRoleArn) - creds := stscreds.NewCredentials(sess, config.StsAssumeRoleArn) + creds := stscreds.NewCredentials(sess, config.StsAssumeRoleArn, func(provider *stscreds.AssumeRoleProvider) { + if config.StsAssumeRoleArn != "" { + provider.ExternalID = &config.StsExternalID + } + }) conf.Credentials = creds sess, err = session.NewSession(conf) if err != nil { diff --git a/server/logging/firehose.go b/server/logging/firehose.go index 850cbd861e..313e4d5cad 100644 --- a/server/logging/firehose.go +++ b/server/logging/firehose.go @@ -37,7 +37,7 @@ type firehoseLogWriter struct { logger log.Logger } -func NewFirehoseLogWriter(region, endpointURL, id, secret, stsAssumeRoleArn, stream string, logger log.Logger) (*firehoseLogWriter, error) { +func NewFirehoseLogWriter(region, endpointURL, id, secret, stsAssumeRoleArn, stsExternalID, stream string, logger log.Logger) (*firehoseLogWriter, error) { conf := &aws.Config{ Region: ®ion, Endpoint: &endpointURL, // empty string or nil will use default values @@ -55,7 +55,11 @@ func NewFirehoseLogWriter(region, endpointURL, id, secret, stsAssumeRoleArn, str } if stsAssumeRoleArn != "" { - creds := stscreds.NewCredentials(sess, stsAssumeRoleArn) + creds := stscreds.NewCredentials(sess, stsAssumeRoleArn, func(provider *stscreds.AssumeRoleProvider) { + if stsExternalID != "" { + provider.ExternalID = &stsExternalID + } + }) conf.Credentials = creds sess, err = session.NewSession(conf) diff --git a/server/logging/kinesis.go b/server/logging/kinesis.go index d1c29c4da9..1faa98eefa 100644 --- a/server/logging/kinesis.go +++ b/server/logging/kinesis.go @@ -39,7 +39,7 @@ type kinesisLogWriter struct { rand *rand.Rand } -func NewKinesisLogWriter(region, endpointURL, id, secret, stsAssumeRoleArn, stream string, logger log.Logger) (*kinesisLogWriter, error) { +func NewKinesisLogWriter(region, endpointURL, id, secret, stsAssumeRoleArn, stsExternalID, stream string, logger log.Logger) (*kinesisLogWriter, error) { conf := &aws.Config{ Region: ®ion, Endpoint: &endpointURL, // empty string or nil will use default values @@ -57,7 +57,11 @@ func NewKinesisLogWriter(region, endpointURL, id, secret, stsAssumeRoleArn, stre } if stsAssumeRoleArn != "" { - creds := stscreds.NewCredentials(sess, stsAssumeRoleArn) + creds := stscreds.NewCredentials(sess, stsAssumeRoleArn, func(provider *stscreds.AssumeRoleProvider) { + if stsExternalID != "" { + provider.ExternalID = &stsExternalID + } + }) conf.Credentials = creds sess, err = session.NewSession(conf) diff --git a/server/logging/lambda.go b/server/logging/lambda.go index bb6f674ef7..afeb645a8e 100644 --- a/server/logging/lambda.go +++ b/server/logging/lambda.go @@ -30,7 +30,7 @@ type lambdaLogWriter struct { logger log.Logger } -func NewLambdaLogWriter(region, id, secret, stsAssumeRoleArn, functionName string, logger log.Logger) (*lambdaLogWriter, error) { +func NewLambdaLogWriter(region, id, secret, stsAssumeRoleArn, stsExternalID, functionName string, logger log.Logger) (*lambdaLogWriter, error) { conf := &aws.Config{ Region: ®ion, } @@ -47,7 +47,11 @@ func NewLambdaLogWriter(region, id, secret, stsAssumeRoleArn, functionName strin } if stsAssumeRoleArn != "" { - creds := stscreds.NewCredentials(sess, stsAssumeRoleArn) + creds := stscreds.NewCredentials(sess, stsAssumeRoleArn, func(provider *stscreds.AssumeRoleProvider) { + if stsExternalID != "" { + provider.ExternalID = &stsExternalID + } + }) conf.Credentials = creds sess, err = session.NewSession(conf) diff --git a/server/logging/logging.go b/server/logging/logging.go index ed087e2087..712525db90 100644 --- a/server/logging/logging.go +++ b/server/logging/logging.go @@ -27,6 +27,7 @@ type FirehoseConfig struct { AccessKeyID string SecretAccessKey string StsAssumeRoleArn string + StsExternalID string } type KinesisConfig struct { @@ -37,6 +38,7 @@ type KinesisConfig struct { AccessKeyID string SecretAccessKey string StsAssumeRoleArn string + StsExternalID string } type LambdaConfig struct { @@ -46,6 +48,7 @@ type LambdaConfig struct { AccessKeyID string SecretAccessKey string StsAssumeRoleArn string + StsExternalID string } type PubSubConfig struct { @@ -104,6 +107,7 @@ func NewJSONLogger(name string, config Config, logger log.Logger) (fleet.JSONLog config.Firehose.AccessKeyID, config.Firehose.SecretAccessKey, config.Firehose.StsAssumeRoleArn, + config.Firehose.StsExternalID, config.Firehose.StreamName, logger, ) @@ -118,6 +122,7 @@ func NewJSONLogger(name string, config Config, logger log.Logger) (fleet.JSONLog config.Kinesis.AccessKeyID, config.Kinesis.SecretAccessKey, config.Kinesis.StsAssumeRoleArn, + config.Kinesis.StsExternalID, config.Kinesis.StreamName, logger, ) @@ -131,6 +136,7 @@ func NewJSONLogger(name string, config Config, logger log.Logger) (fleet.JSONLog config.Lambda.AccessKeyID, config.Lambda.SecretAccessKey, config.Lambda.StsAssumeRoleArn, + config.Lambda.StsExternalID, config.Lambda.Function, logger, ) diff --git a/server/mail/mail.go b/server/mail/mail.go index 8e5ea5c7e8..dcf89b9f5f 100644 --- a/server/mail/mail.go +++ b/server/mail/mail.go @@ -20,7 +20,14 @@ import ( func NewService(config config.FleetConfig) (fleet.MailService, error) { switch strings.ToLower(config.Email.EmailBackend) { case "ses": - return NewSESSender(config.SES.Region, config.SES.EndpointURL, config.SES.AccessKeyID, config.SES.SecretAccessKey, config.SES.StsAssumeRoleArn, config.SES.SourceArn) + return NewSESSender(config.SES.Region, + config.SES.EndpointURL, + config.SES.AccessKeyID, + config.SES.SecretAccessKey, + config.SES.StsAssumeRoleArn, + config.SES.StsExternalID, + config.SES.SourceArn, + ) default: return &mailService{}, nil } diff --git a/server/mail/ses.go b/server/mail/ses.go index 9ba22c3274..0c38d84a23 100644 --- a/server/mail/ses.go +++ b/server/mail/ses.go @@ -41,7 +41,7 @@ func (s *sesSender) SendEmail(e fleet.Email) error { return s.sendMail(e, msg) } -func NewSESSender(region, endpointURL, id, secret, stsAssumeRoleArn, sourceArn string) (*sesSender, error) { +func NewSESSender(region, endpointURL, id, secret, stsAssumeRoleArn, stsExternalID, sourceArn string) (*sesSender, error) { conf := &aws.Config{ Region: ®ion, Endpoint: &endpointURL, // empty string or nil will use default values @@ -59,7 +59,11 @@ func NewSESSender(region, endpointURL, id, secret, stsAssumeRoleArn, sourceArn s } if stsAssumeRoleArn != "" { - creds := stscreds.NewCredentials(sess, stsAssumeRoleArn) + creds := stscreds.NewCredentials(sess, stsAssumeRoleArn, func(provider *stscreds.AssumeRoleProvider) { + if stsExternalID != "" { + provider.ExternalID = &stsExternalID + } + }) conf.Credentials = creds sess, err = session.NewSession(conf) diff --git a/terraform/addons/byo-file-carving/carving/README.md b/terraform/addons/byo-file-carving/carving/README.md index 3a68cdb595..8958b8469b 100644 --- a/terraform/addons/byo-file-carving/carving/README.md +++ b/terraform/addons/byo-file-carving/carving/README.md @@ -44,6 +44,7 @@ No modules. | [s3\_bucket\_name](#input\_s3\_bucket\_name) | The S3 bucket for carve results to be written to | `string` | n/a | yes | | [s3\_bucket\_region](#input\_s3\_bucket\_region) | The S3 bucket region | `string` | n/a | yes | | [s3\_carve\_prefix](#input\_s3\_carve\_prefix) | The S3 object prefix to use when storing carve results | `string` | `""` | no | +| [sts\_external\_id](#input\_sts\_external\_id) | Optional unique identifier that can be used by the principal assuming the role to assert its identity. | `string` | `""` | no | ## Outputs diff --git a/terraform/addons/byo-file-carving/carving/outputs.tf b/terraform/addons/byo-file-carving/carving/outputs.tf index db2fb4259d..d3b11ff580 100644 --- a/terraform/addons/byo-file-carving/carving/outputs.tf +++ b/terraform/addons/byo-file-carving/carving/outputs.tf @@ -1,6 +1,7 @@ output "fleet_extra_environment_variables" { value = { FLEET_S3_STS_ASSUME_ROLE_ARN = var.iam_role_arn + FLEET_S3_STS_EXTERNAL_ID = var.sts_external_id FLEET_S3_BUCKET = var.s3_bucket_name FLEET_S3_REGION = var.s3_bucket_region FLEET_S3_PREFIX = var.s3_carve_prefix diff --git a/terraform/addons/byo-file-carving/carving/variables.tf b/terraform/addons/byo-file-carving/carving/variables.tf index 61cf5c28a9..054e72fe7b 100644 --- a/terraform/addons/byo-file-carving/carving/variables.tf +++ b/terraform/addons/byo-file-carving/carving/variables.tf @@ -3,6 +3,12 @@ variable "iam_role_arn" { description = "IAM Role ARN to assume into for file carving uploads to S3" } +variable "sts_external_id" { + type = string + description = "Optional unique identifier that can be used by the principal assuming the role to assert its identity." + default = "" +} + variable "s3_bucket_name" { type = string description = "The S3 bucket for carve results to be written to" diff --git a/terraform/addons/byo-file-carving/target-account/README.md b/terraform/addons/byo-file-carving/target-account/README.md index 3a7462830c..456dfd077e 100644 --- a/terraform/addons/byo-file-carving/target-account/README.md +++ b/terraform/addons/byo-file-carving/target-account/README.md @@ -47,6 +47,7 @@ No modules. | [aws_iam_role_policy_attachment.s3_access_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_kms_key.s3_encryption_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_s3_bucket.carve_results_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_public_access_block.carve_results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_server_side_encryption_configuration.sse](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -59,6 +60,7 @@ No modules. |------|-------------|------|---------|:--------:| | [bucket\_name](#input\_bucket\_name) | The name of the osquery carve results bucket | `string` | n/a | yes | | [fleet\_iam\_role\_arn](#input\_fleet\_iam\_role\_arn) | The IAM role ARN of the Fleet service | `string` | n/a | yes | +| [sts\_external\_id](#input\_sts\_external\_id) | Optional unique identifier that can be used by the principal assuming the role to assert its identity. | `string` | `""` | no | ## Outputs diff --git a/terraform/addons/byo-file-carving/target-account/s3.tf b/terraform/addons/byo-file-carving/target-account/s3.tf index 7afa5d4910..a3e4dd53aa 100644 --- a/terraform/addons/byo-file-carving/target-account/s3.tf +++ b/terraform/addons/byo-file-carving/target-account/s3.tf @@ -90,6 +90,14 @@ data "aws_iam_policy_document" "assume_role" { identifiers = [var.fleet_iam_role_arn] type = "AWS" } + dynamic "condition" { + for_each = length(var.sts_external_id) > 0 ? [1] : [] + content { + test = "StringEquals" + variable = "sts:ExternalId" + values = [var.sts_external_id] + } + } } } diff --git a/terraform/addons/byo-file-carving/target-account/variables.tf b/terraform/addons/byo-file-carving/target-account/variables.tf index 0b10d49fd4..91c716b7da 100644 --- a/terraform/addons/byo-file-carving/target-account/variables.tf +++ b/terraform/addons/byo-file-carving/target-account/variables.tf @@ -6,4 +6,10 @@ variable "bucket_name" { variable "fleet_iam_role_arn" { type = string description = "The IAM role ARN of the Fleet service" +} + +variable "sts_external_id" { + type = string + description = "Optional unique identifier that can be used by the principal assuming the role to assert its identity." + default = "" } \ No newline at end of file diff --git a/terraform/addons/byo-firehose-logging-destination/firehose/README.md b/terraform/addons/byo-firehose-logging-destination/firehose/README.md index 83d64b8dcd..49bf993112 100644 --- a/terraform/addons/byo-firehose-logging-destination/firehose/README.md +++ b/terraform/addons/byo-firehose-logging-destination/firehose/README.md @@ -1,4 +1,3 @@ - ## Requirements | Name | Version | @@ -22,15 +21,17 @@ No modules. |------|------| | [aws_iam_policy.fleet-assume-role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy_document.fleet-assume-role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [firehose\_audit\_name](#input\_firehose\_audit\_name) | name of the firehose delivery stream for fleet audit logs | `string` | n/a | yes | | [firehose\_results\_name](#input\_firehose\_results\_name) | name of the firehose delivery stream for osquery results logs | `string` | n/a | yes | | [firehose\_status\_name](#input\_firehose\_status\_name) | name of the firehose delivery stream for osquery status logs | `string` | n/a | yes | | [iam\_role\_arn](#input\_iam\_role\_arn) | IAM Role ARN to use for Firehose destination logging | `string` | n/a | yes | +| [region](#input\_region) | region the target firehose delivery stream is in | `string` | n/a | yes | +| [sts\_external\_id](#input\_sts\_external\_id) | Optional unique identifier that can be used by the principal assuming the role to assert its identity. | `string` | `""` | no | ## Outputs @@ -38,4 +39,3 @@ No modules. |------|-------------| | [fleet\_extra\_environment\_variables](#output\_fleet\_extra\_environment\_variables) | n/a | | [fleet\_extra\_iam\_policies](#output\_fleet\_extra\_iam\_policies) | n/a | - \ No newline at end of file diff --git a/terraform/addons/byo-firehose-logging-destination/firehose/outputs.tf b/terraform/addons/byo-firehose-logging-destination/firehose/outputs.tf index 3e6b13c1d1..708a3e5cc9 100644 --- a/terraform/addons/byo-firehose-logging-destination/firehose/outputs.tf +++ b/terraform/addons/byo-firehose-logging-destination/firehose/outputs.tf @@ -4,6 +4,7 @@ output "fleet_extra_environment_variables" { FLEET_FIREHOSE_RESULT_STREAM = var.firehose_results_name FLEET_FIREHOSE_AUDIT_STREAM = var.firehose_audit_name FLEET_FIREHOSE_STS_ASSUME_ROLE_ARN = var.iam_role_arn + FLEET_FIREHOSE_STS_EXTERNAL_ID = var.sts_external_id FLEET_FIREHOSE_REGION = var.region FLEET_OSQUERY_STATUS_LOG_PLUGIN = "firehose" FLEET_OSQUERY_RESULT_LOG_PLUGIN = "firehose" diff --git a/terraform/addons/byo-firehose-logging-destination/firehose/variables.tf b/terraform/addons/byo-firehose-logging-destination/firehose/variables.tf index c7c8161e34..50bc040469 100644 --- a/terraform/addons/byo-firehose-logging-destination/firehose/variables.tf +++ b/terraform/addons/byo-firehose-logging-destination/firehose/variables.tf @@ -22,3 +22,9 @@ variable "region" { type = string description = "region the target firehose delivery stream is in" } + +variable "sts_external_id" { + type = string + description = "Optional unique identifier that can be used by the principal assuming the role to assert its identity." + default = "" +} diff --git a/terraform/addons/byo-firehose-logging-destination/target-account/README.md b/terraform/addons/byo-firehose-logging-destination/target-account/README.md index fe91b4f304..b4eb9af91b 100644 --- a/terraform/addons/byo-firehose-logging-destination/target-account/README.md +++ b/terraform/addons/byo-firehose-logging-destination/target-account/README.md @@ -54,9 +54,10 @@ No modules. |------|-------------|------|---------|:--------:| | [fleet\_iam\_role\_arn](#input\_fleet\_iam\_role\_arn) | the arn of the fleet role that firehose will assume to write data to your bucket | `string` | n/a | yes | | [kms\_key\_arn](#input\_kms\_key\_arn) | An optional KMS key ARN for server-side encryption. If not provided and encryption is enabled, a new key will be created. | `string` | `""` | no | -| [log\_destinations](#input\_log\_destinations) | A map of configurations for Firehose delivery streams. |
map(object({
name = string
prefix = string
error_output_prefix = string
buffering_size = number
buffering_interval = number
compression_format = string
}))
|
{
"audit": {
"buffering_interval": 120,
"buffering_size": 20,
"compression_format": "UNCOMPRESSED",
"error_output_prefix": "audit/error/error=!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/",
"name": "fleet_audit",
"prefix": "audit/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"
},
"results": {
"buffering_interval": 120,
"buffering_size": 20,
"compression_format": "UNCOMPRESSED",
"error_output_prefix": "results/error/error=!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/",
"name": "osquery_results",
"prefix": "results/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"
},
"status": {
"buffering_interval": 120,
"buffering_size": 20,
"compression_format": "UNCOMPRESSED",
"error_output_prefix": "status/error/error=!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/",
"name": "osquery_status",
"prefix": "status/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"
}
}
| no | +| [log\_destinations](#input\_log\_destinations) | A map of configurations for Firehose delivery streams. |
map(object({
name = string
prefix = string
error_output_prefix = string
buffering_size = number
buffering_interval = number
compression_format = string
}))
|
{
"audit": {
"buffering_interval": 120,
"buffering_size": 20,
"compression_format": "UNCOMPRESSED",
"error_output_prefix": "audit/error/error=!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/",
"name": "fleet_audit",
"prefix": "audit/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"
},
"results": {
"buffering_interval": 120,
"buffering_size": 20,
"compression_format": "UNCOMPRESSED",
"error_output_prefix": "results/error/error=!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/",
"name": "osquery_results",
"prefix": "results/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"
},
"status": {
"buffering_interval": 120,
"buffering_size": 20,
"compression_format": "UNCOMPRESSED",
"error_output_prefix": "status/error/error=!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/",
"name": "osquery_status",
"prefix": "status/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"
}
}
| no | | [osquery\_logging\_destination\_bucket\_name](#input\_osquery\_logging\_destination\_bucket\_name) | name of the bucket to store osquery results & status logs | `string` | n/a | yes | | [server\_side\_encryption\_enabled](#input\_server\_side\_encryption\_enabled) | A boolean flag to enable/disable server-side encryption. Defaults to true (enabled). | `bool` | `true` | no | +| [sts\_external\_id](#input\_sts\_external\_id) | Optional unique identifier that can be used by the principal assuming the role to assert its identity. | `string` | `""` | no | ## Outputs diff --git a/terraform/addons/byo-firehose-logging-destination/target-account/iam.tf b/terraform/addons/byo-firehose-logging-destination/target-account/iam.tf index aed5f2036d..8f17b0b527 100644 --- a/terraform/addons/byo-firehose-logging-destination/target-account/iam.tf +++ b/terraform/addons/byo-firehose-logging-destination/target-account/iam.tf @@ -10,6 +10,14 @@ data "aws_iam_policy_document" "assume_role" { identifiers = [var.fleet_iam_role_arn] type = "AWS" } + dynamic "condition" { + for_each = length(var.sts_external_id) > 0 ? [1] : [] + content { + test = "StringEquals" + variable = "sts:ExternalId" + values = [var.sts_external_id] + } + } } } diff --git a/terraform/addons/byo-firehose-logging-destination/target-account/variables.tf b/terraform/addons/byo-firehose-logging-destination/target-account/variables.tf index 99215bddb5..c44c28cf02 100644 --- a/terraform/addons/byo-firehose-logging-destination/target-account/variables.tf +++ b/terraform/addons/byo-firehose-logging-destination/target-account/variables.tf @@ -8,15 +8,21 @@ variable "fleet_iam_role_arn" { description = "The ARN of the IAM role that will be assumed to gain permissions required to write to the Kinesis Firehose delivery stream." } +variable "sts_external_id" { + type = string + description = "Optional unique identifier that can be used by the principal assuming the role to assert its identity." + default = "" +} + variable "log_destinations" { description = "A map of configurations for Firehose delivery streams." type = map(object({ - name = string - prefix = string - error_output_prefix = string - buffering_size = number - buffering_interval = number - compression_format = string + name = string + prefix = string + error_output_prefix = string + buffering_size = number + buffering_interval = number + compression_format = string })) default = { results = {