Skip to content

Commit

Permalink
Cloudfront URL config changes (#25145)
Browse files Browse the repository at this point in the history
For #24868 (subtask)

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
  • Loading branch information
getvictor authored Jan 6, 2025
1 parent 80520db commit e0d0e80
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 38 deletions.
4 changes: 4 additions & 0 deletions changes/23823-cloudfront-cdn
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Allow delivery of bootstrap packages and software installers using signed URLs from CloudFront CDN. To enable, configure server settings:
- s3_software_installers_cloudfront_url
- s3_software_installers_cloudfront_url_signing_public_key_id
- s3_software_installers_cloudfront_url_signing_private_key
37 changes: 19 additions & 18 deletions cmd/fleet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,24 +104,25 @@ func applyDevFlags(cfg *config.FleetConfig) {
cfg.Prometheus.BasicAuth.Password = "insecure"
}

cfg.S3 = config.S3Config{
CarvesBucket: "carves-dev",
CarvesRegion: "minio",
CarvesPrefix: "dev-prefix",
CarvesEndpointURL: "localhost:9000",
CarvesAccessKeyID: "minio",
CarvesSecretAccessKey: "minio123!",
CarvesDisableSSL: true,
CarvesForceS3PathStyle: true,

SoftwareInstallersBucket: "software-installers-dev",
SoftwareInstallersRegion: "minio",
SoftwareInstallersPrefix: "dev-prefix",
SoftwareInstallersEndpointURL: "localhost:9000",
SoftwareInstallersAccessKeyID: "minio",
SoftwareInstallersSecretAccessKey: "minio123!",
SoftwareInstallersDisableSSL: true,
SoftwareInstallersForceS3PathStyle: true,
cfg.S3.CarvesBucket = "carves-dev"
cfg.S3.CarvesRegion = "minio"
cfg.S3.CarvesPrefix = "dev-prefix"
cfg.S3.CarvesEndpointURL = "localhost:9000"
cfg.S3.CarvesAccessKeyID = "minio"
cfg.S3.CarvesSecretAccessKey = "minio123!"
cfg.S3.CarvesDisableSSL = true
cfg.S3.CarvesForceS3PathStyle = true

// Allow the software installers bucket to be overridden in dev mode
if cfg.S3.SoftwareInstallersBucket == "" {
cfg.S3.SoftwareInstallersBucket = "software-installers-dev"
cfg.S3.SoftwareInstallersRegion = "minio"
cfg.S3.SoftwareInstallersPrefix = "dev-prefix"
cfg.S3.SoftwareInstallersEndpointURL = "localhost:9000"
cfg.S3.SoftwareInstallersAccessKeyID = "minio"
cfg.S3.SoftwareInstallersSecretAccessKey = "minio123!"
cfg.S3.SoftwareInstallersDisableSSL = true
cfg.S3.SoftwareInstallersForceS3PathStyle = true
}

cfg.Packaging.S3 = config.S3Config{
Expand Down
2 changes: 2 additions & 0 deletions cmd/fleet/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,8 @@ the way that the Fleet server works.
}
bootstrapPackageStore = bstore
level.Info(logger).Log("msg", "using S3 bootstrap package store", "bucket", config.S3.SoftwareInstallersBucket)

config.S3.ValidateCloudfrontURL(initFatal)
} else {
installerDir := os.TempDir()
if dir := os.Getenv("FLEET_SOFTWARE_INSTALLER_STORE_DIR"); dir != "" {
Expand Down
78 changes: 58 additions & 20 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,16 +316,48 @@ type S3Config struct {
CarvesDisableSSL bool `yaml:"carves_disable_ssl"`
CarvesForceS3PathStyle bool `yaml:"carves_force_s3_path_style"`

SoftwareInstallersBucket string `yaml:"software_installers_bucket"`
SoftwareInstallersPrefix string `yaml:"software_installers_prefix"`
SoftwareInstallersRegion string `yaml:"software_installers_region"`
SoftwareInstallersEndpointURL string `yaml:"software_installers_endpoint_url"`
SoftwareInstallersAccessKeyID string `yaml:"software_installers_access_key_id"`
SoftwareInstallersSecretAccessKey string `yaml:"software_installers_secret_access_key"`
SoftwareInstallersStsAssumeRoleArn string `yaml:"software_installers_sts_assume_role_arn"`
SoftwareInstallersStsExternalID string `yaml:"software_installers_sts_external_id"`
SoftwareInstallersDisableSSL bool `yaml:"software_installers_disable_ssl"`
SoftwareInstallersForceS3PathStyle bool `yaml:"software_installers_force_s3_path_style"`
SoftwareInstallersBucket string `yaml:"software_installers_bucket"`
SoftwareInstallersPrefix string `yaml:"software_installers_prefix"`
SoftwareInstallersRegion string `yaml:"software_installers_region"`
SoftwareInstallersEndpointURL string `yaml:"software_installers_endpoint_url"`
SoftwareInstallersAccessKeyID string `yaml:"software_installers_access_key_id"`
SoftwareInstallersSecretAccessKey string `yaml:"software_installers_secret_access_key"`
SoftwareInstallersStsAssumeRoleArn string `yaml:"software_installers_sts_assume_role_arn"`
SoftwareInstallersStsExternalID string `yaml:"software_installers_sts_external_id"`
SoftwareInstallersDisableSSL bool `yaml:"software_installers_disable_ssl"`
SoftwareInstallersForceS3PathStyle bool `yaml:"software_installers_force_s3_path_style"`
SoftwareInstallersCloudfrontURL string `yaml:"software_installers_cloudfront_url"`
SoftwareInstallersCloudfrontURLSigningPublicKeyID string `yaml:"software_installers_cloudfront_url_signing_public_key_id"`
SoftwareInstallersCloudfrontURLSigningPrivateKey string `yaml:"software_installers_cloudfront_url_signing_private_key"`
}

func (s S3Config) ValidateCloudfrontURL(initFatal func(err error, msg string)) {
if s.SoftwareInstallersCloudfrontURL != "" {
cloudfrontURL, err := url.Parse(s.SoftwareInstallersCloudfrontURL)
if err != nil {
initFatal(err, "S3 software installers cloudfront URL")
return
}
if cloudfrontURL.Scheme != "https" {
initFatal(errors.New("cloudfront url scheme must be https"), "S3 software installers cloudfront URL")
return
}
if s.SoftwareInstallersCloudfrontURLSigningPrivateKey != "" && s.SoftwareInstallersCloudfrontURLSigningPublicKeyID == "" ||
s.SoftwareInstallersCloudfrontURLSigningPrivateKey == "" && s.SoftwareInstallersCloudfrontURLSigningPublicKeyID != "" {
initFatal(errors.New("Couldn't configure. Both `s3_software_installers_cloudfront_url_signing_public_key_id` and `s3_software_installers_cloudfront_url_signing_private_key` must be set for URL signing."),
"S3 software installers cloudfront URL")
return
}
if s.SoftwareInstallersCloudfrontURLSigningPrivateKey == "" && s.SoftwareInstallersCloudfrontURLSigningPublicKeyID == "" {
initFatal(errors.New("Couldn't configure. Both `s3_software_installers_cloudfront_url_signing_public_key_id` and `s3_software_installers_cloudfront_url_signing_private_key` must be set when CloudFront distribution URL is set."),
"S3 software installers cloudfront URL")
return
}
} else if s.SoftwareInstallersCloudfrontURLSigningPrivateKey != "" || s.SoftwareInstallersCloudfrontURLSigningPublicKeyID != "" {
initFatal(errors.New("Couldn't configure. `s3_software_installers_cloudfront_url` must be set to use `s3_software_installers_cloudfront_url_signing_public_key_id` and `s3_software_installers_cloudfront_url_signing_private_key`."),
"S3 software installers cloudfront URL")
return
}
}

func (s S3Config) BucketsAndPrefixesMatch() bool {
Expand Down Expand Up @@ -1197,6 +1229,9 @@ func (man Manager) addConfigs() {
man.addConfigString("s3.software_installers_sts_external_id", "", "Optional unique identifier that can be used by the principal assuming the role to assert its identity.")
man.addConfigBool("s3.software_installers_disable_ssl", false, "Disable SSL (typically for local testing)")
man.addConfigBool("s3.software_installers_force_s3_path_style", false, "Set this to true to force path-style addressing, i.e., `http://s3.amazonaws.com/BUCKET/KEY`")
man.addConfigString("s3.software_installers_cloudfront_url", "", "CloudFront URL for software installers")
man.addConfigString("s3.software_installers_cloudfront_url_signing_public_key_id", "", "CloudFront public key ID for URL signing")
man.addConfigString("s3.software_installers_cloudfront_url_signing_private_key", "", "CloudFront private key for URL signing")

// PubSub
man.addConfigString("pubsub.project", "", "Google Cloud Project to use")
Expand Down Expand Up @@ -1622,16 +1657,19 @@ func (man Manager) loadS3Config() S3Config {
DisableSSL: man.getConfigBool("s3.disable_ssl"),
ForceS3PathStyle: man.getConfigBool("s3.force_s3_path_style"),

SoftwareInstallersBucket: man.getConfigString("s3.software_installers_bucket"),
SoftwareInstallersPrefix: man.getConfigString("s3.software_installers_prefix"),
SoftwareInstallersRegion: man.getConfigString("s3.software_installers_region"),
SoftwareInstallersEndpointURL: man.getConfigString("s3.software_installers_endpoint_url"),
SoftwareInstallersAccessKeyID: man.getConfigString("s3.software_installers_access_key_id"),
SoftwareInstallersSecretAccessKey: man.getConfigString("s3.software_installers_secret_access_key"),
SoftwareInstallersStsAssumeRoleArn: man.getConfigString("s3.software_installers_sts_assume_role_arn"),
SoftwareInstallersStsExternalID: man.getConfigString("s3.software_installers_sts_external_id"),
SoftwareInstallersDisableSSL: man.getConfigBool("s3.software_installers_disable_ssl"),
SoftwareInstallersForceS3PathStyle: man.getConfigBool("s3.software_installers_force_s3_path_style"),
SoftwareInstallersBucket: man.getConfigString("s3.software_installers_bucket"),
SoftwareInstallersPrefix: man.getConfigString("s3.software_installers_prefix"),
SoftwareInstallersRegion: man.getConfigString("s3.software_installers_region"),
SoftwareInstallersEndpointURL: man.getConfigString("s3.software_installers_endpoint_url"),
SoftwareInstallersAccessKeyID: man.getConfigString("s3.software_installers_access_key_id"),
SoftwareInstallersSecretAccessKey: man.getConfigString("s3.software_installers_secret_access_key"),
SoftwareInstallersStsAssumeRoleArn: man.getConfigString("s3.software_installers_sts_assume_role_arn"),
SoftwareInstallersStsExternalID: man.getConfigString("s3.software_installers_sts_external_id"),
SoftwareInstallersDisableSSL: man.getConfigBool("s3.software_installers_disable_ssl"),
SoftwareInstallersForceS3PathStyle: man.getConfigBool("s3.software_installers_force_s3_path_style"),
SoftwareInstallersCloudfrontURL: man.getConfigString("s3.software_installers_cloudfront_url"),
SoftwareInstallersCloudfrontURLSigningPublicKeyID: man.getConfigString("s3.software_installers_cloudfront_url_signing_public_key_id"),
SoftwareInstallersCloudfrontURLSigningPrivateKey: man.getConfigString("s3.software_installers_cloudfront_url_signing_private_key"),
}
}

Expand Down
41 changes: 41 additions & 0 deletions server/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,3 +695,44 @@ e+Z1cALnWREYhEPv4JrR5U0VvqeIdExDD6Ida61yvd7oc59pn0kpfKjozPJr6FsU
// prevent static analysis tools from raising issues due to detection of private key
// in code.
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }

func TestValidateCloudfrontURL(t *testing.T) {
t.Parallel()
cases := []struct {
name string
url string
publicKey string
privateKey string
errMatches string
}{
{"happy path", "https://example.com", "public", "private", ""},
{"bad URL", "bozo!://example.com", "public", "private", "parse"},
{"non-HTTPS URL", "http://example.com", "public", "private", "cloudfront url scheme must be https"},
{"missing URL", "", "public", "private", "`s3_software_installers_cloudfront_url` must be set"},
{"missing public key", "https://example.com", "", "private",
"Both `s3_software_installers_cloudfront_url_signing_public_key_id` and `s3_software_installers_cloudfront_url_signing_private_key` must be set"},
{"missing private key", "https://example.com", "public", "",
"Both `s3_software_installers_cloudfront_url_signing_public_key_id` and `s3_software_installers_cloudfront_url_signing_private_key` must be set"},
{"missing keys", "https://example.com", "", "",
"Both `s3_software_installers_cloudfront_url_signing_public_key_id` and `s3_software_installers_cloudfront_url_signing_private_key` must be set"},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s3 := S3Config{
SoftwareInstallersCloudfrontURL: c.url,
SoftwareInstallersCloudfrontURLSigningPublicKeyID: c.publicKey,
SoftwareInstallersCloudfrontURLSigningPrivateKey: c.privateKey,
}
initFatal := func(err error, msg string) {
if c.errMatches != "" {
require.Error(t, err)
require.Regexp(t, c.errMatches, err.Error())
} else {
t.Errorf("unexpected error: %v", err)
}
}
s3.ValidateCloudfrontURL(initFatal)
})
}
}

0 comments on commit e0d0e80

Please sign in to comment.