StartCredentialRefresh starts a background goroutine that periodically refreshes S3 credentials for long-lived DuckDB connections using the credential_chain provider. This prevents credential expiration when running on EC2 with IAM instance roles, STS assume-role, or other temporary credential sourc
(execer sqlExecer, dlCfg DuckLakeConfig, isTxActive ...func() bool)
| 2314 | if dlCfg.S3Chain != "" { |
| 2315 | secret += fmt.Sprintf(",\n\t\t\tCHAIN '%s'", dlCfg.S3Chain) |
| 2316 | } |
| 2317 | |
| 2318 | // Add profile if specified (for config chain) |
| 2319 | if dlCfg.S3Profile != "" { |
| 2320 | secret += fmt.Sprintf(",\n\t\t\tPROFILE '%s'", dlCfg.S3Profile) |
| 2321 | } |
| 2322 | |
| 2323 | // Add region override if specified |
| 2324 | if dlCfg.S3Region != "" { |
| 2325 | secret += fmt.Sprintf(",\n\t\t\tREGION '%s'", dlCfg.S3Region) |
| 2326 | } |
| 2327 | |
| 2328 | // Set URL style and SSL on the secret itself. duckdb-httpfs only honors |
| 2329 | // these from the secret (or env vars if the env-var-for-secret-settings |
| 2330 | // flag is on) — `SET GLOBAL s3_use_ssl = ...` at the session level is |
| 2331 | // dropped by S3KeyValueReader::TryGetSecretKeyOrSetting because it |
| 2332 | // filters GLOBAL-scope settings unless the env-var path is enabled. |
| 2333 | // Setting it on the secret is the only knob that actually controls the |
| 2334 | // http_proto = use_ssl ? "https://" : "http://" decision in s3fs.cpp. |
| 2335 | if dlCfg.S3Endpoint != "" || dlCfg.HTTPProxy != "" { |
| 2336 | if dlCfg.S3Endpoint != "" { |
| 2337 | secret += fmt.Sprintf(",\n\t\t\tENDPOINT '%s'", dlCfg.S3Endpoint) |
| 2338 | } |
| 2339 | urlStyle, useSSL := resolveS3SecretTransport(dlCfg) |
| 2340 | secret += fmt.Sprintf(",\n\t\t\tURL_STYLE '%s'", urlStyle) |
| 2341 | secret += fmt.Sprintf(",\n\t\t\tUSE_SSL %s", useSSL) |
| 2342 | } |
| 2343 | |
| 2344 | secret += "\n\t\t)" |
| 2345 | return secret |
| 2346 | } |
| 2347 | |
| 2348 | // fetchAWSSDKCredentials uses the Go AWS SDK's default credential chain to retrieve |
| 2349 | // temporary credentials. This supports all credential sources that the Go SDK supports, |
| 2350 | // including EKS Pod Identity (AWS_CONTAINER_CREDENTIALS_FULL_URI), IRSA, instance |
| 2351 | // metadata, environment variables, and config files — unlike DuckDB's built-in |
| 2352 | // credential_chain which does not support EKS Pod Identity. |
| 2353 | func fetchAWSSDKCredentials(ctx context.Context, region string) (aws.Credentials, error) { |
| 2354 | var opts []func(*awsconfig.LoadOptions) error |
| 2355 | if region != "" { |
| 2356 | opts = append(opts, awsconfig.WithRegion(region)) |
| 2357 | } |
| 2358 | cfg, err := awsconfig.LoadDefaultConfig(ctx, opts...) |
| 2359 | if err != nil { |
| 2360 | return aws.Credentials{}, fmt.Errorf("failed to load AWS config: %w", err) |
| 2361 | } |
| 2362 | creds, err := cfg.Credentials.Retrieve(ctx) |
| 2363 | if err != nil { |
| 2364 | return aws.Credentials{}, fmt.Errorf("failed to retrieve AWS credentials: %w", err) |
| 2365 | } |
| 2366 | return creds, nil |
| 2367 | } |
| 2368 | |
| 2369 | // buildAWSSdkSecret fetches credentials via the Go AWS SDK and builds a |
| 2370 | // CREATE SECRET statement with PROVIDER config using the explicit temporary credentials. |
| 2371 | func buildAWSSdkSecret(ctx context.Context, dlCfg DuckLakeConfig) (string, error) { |
| 2372 | creds, err := fetchAWSSDKCredentials(ctx, dlCfg.S3Region) |
| 2373 | if err != nil { |