Skip to content

feat: add support for AWS RDS IAM authentication and update dependencies #3086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions db/sql/SqlDb.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"regexp"
"strconv"
"strings"
"time"

"github.com/Masterminds/squirrel"
"github.com/go-gorp/gorp/v3"
Expand Down Expand Up @@ -364,6 +365,12 @@ func (d *SqlDb) Connect(_ string) {
panic(err)
}

if cfg.Dialect == util.DbDriverPostgres && cfg.PostgresIAM {
maxLifetime := 14 * time.Minute
log.Debugf("Setting connection max lifetime to %s for IAM authentication", maxLifetime)
sqlDb.SetConnMaxLifetime(maxLifetime)
}

var dialect gorp.Dialect

switch cfg.Dialect {
Expand Down
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.24.0

require (
github.com/Masterminds/squirrel v1.5.4
github.com/aws/aws-sdk-go-v2/config v1.27.7
github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.3
github.com/coreos/go-oidc/v3 v3.14.1
github.com/creack/pty v1.1.24
github.com/go-git/go-git/v5 v5.16.0
Expand Down Expand Up @@ -38,6 +40,18 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/aws/aws-sdk-go-v2 v1.25.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.7 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 // indirect
github.com/aws/smithy-go v1.20.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
Expand Down
28 changes: 28 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.25.3 h1:xYiLpZTQs1mzvz5PaI6uR0Wh57ippuEthxS4iK5v0n0=
github.com/aws/aws-sdk-go-v2 v1.25.3/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I=
github.com/aws/aws-sdk-go-v2/config v1.27.7 h1:JSfb5nOQF01iOgxFI5OIKWwDiEXWTyTgg1Mm1mHi0A4=
github.com/aws/aws-sdk-go-v2/config v1.27.7/go.mod h1:PH0/cNpoMO+B04qET699o5W92Ca79fVtbUnvMIZro4I=
github.com/aws/aws-sdk-go-v2/credentials v1.17.7 h1:WJd+ubWKoBeRh7A5iNMnxEOs982SyVKOJD+K8HIezu4=
github.com/aws/aws-sdk-go-v2/credentials v1.17.7/go.mod h1:UQi7LMR0Vhvs+44w5ec8Q+VS+cd10cjwgHwiVkE0YGU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 h1:p+y7FvkK2dxS+FEwRIDHDe//ZX+jDhP8HHE50ppj4iI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3/go.mod h1:/fYB+FZbDlwlAiynK9KDXlzZl3ANI9JkD0Uhz5FjNT4=
github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.3 h1:mfxA6HX/mla8BrjVHdVD0G49+0Z+xKel//NCPBk0qbo=
github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.3/go.mod h1:PjvlBlYNNXPrMAGarXrnV+UYv1T9XyTT2Ono41NQjq8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3 h1:ifbIbHZyGl1alsAhPIYsHOg5MuApgqOvVeI8wIugXfs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3/go.mod h1:oQZXg3c6SNeY6OZrDY+xHcF4VGIEoNotX2B4PrDeoJI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3 h1:Qvodo9gHG9F3E8SfYOspPeBt0bjSbsevK8WhRAUHcoY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3/go.mod h1:vCKrdLXtybdf/uQd/YfVR2r5pcbNuEYKzMQpcxmeSJw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 h1:K/NXvIftOlX+oGgWGIa3jDyYLDNsdVhsjHmsBH2GLAQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5/go.mod h1:cl9HGLV66EnCmMNzq4sYOti+/xo8w34CsgzVtm2GgsY=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 h1:XOPfar83RIRPEzfihnp+U6udOveKZJvPQ76SKWrLRHc=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.2/go.mod h1:Vv9Xyk1KMHXrR3vNQe8W5LMFdTjSeWk0gBZBzvf3Qa0=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 h1:pi0Skl6mNl2w8qWZXcdOyg197Zsf4G97U7Sso9JXGZE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2/go.mod h1:JYzLoEVeLXk+L4tn1+rrkfhkxl6mLDEVaDSvGq9og90=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 h1:Ppup1nVNAOWbBOrcoOxaxPeEnSFB2RnnQdguhXpmeQk=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.4/go.mod h1:+K1rNPVyGxkRuv9NNiaZ4YhBFuyw2MMA9SlIJ1Zlpz8=
github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
Expand Down
56 changes: 56 additions & 0 deletions util/aws_iam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package util

import (
"context"
"strings"

"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/rds/auth"
log "github.com/sirupsen/logrus"
)

func GetRDSAuthToken(hostname string, username string, region string) (string, error) {
hostname, port := ExtractHostPort(hostname, "5432")
log.Debugf("Generating RDS auth token for %s:%s, user %s in region %s", hostname, port, username, region)

cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
if err != nil {
log.Errorf("Failed to load AWS config: %v", err)
return "", err
}

credProvider := cfg.Credentials

creds, err := credProvider.Retrieve(context.TODO())
if err != nil {
log.Errorf("Failed to retrieve AWS credentials: %v", err)
return "", err
}

log.Debugf("AWS credentials retrieved successfully. Access key ID: %s, Provider: %s",
creds.AccessKeyID[:4]+"...", creds.Source)

log.Debugf("Building auth token for endpoint %s:%s in region %s", hostname, port, region)
authToken, err := auth.BuildAuthToken(
context.TODO(),
hostname+":"+port,
region,
username,
cfg.Credentials,
)
if err != nil {
log.Errorf("Failed to generate RDS auth token: %v", err)
return "", err
}

log.Debugf("Successfully generated RDS auth token (length: %d)", len(authToken))
return authToken, nil
}

func ExtractHostPort(hostPort string, defaultPort string) (host string, port string) {
parts := strings.Split(hostPort, ":")
if len(parts) == 1 {
return parts[0], defaultPort
}
return parts[0], parts[1]
}
26 changes: 21 additions & 5 deletions util/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ const (
type DbConfig struct {
Dialect string `json:"-"`

Hostname string `json:"host,omitempty" env:"SEMAPHORE_DB_HOST" default:"0.0.0.0"`
Username string `json:"user,omitempty" env:"SEMAPHORE_DB_USER"`
Password string `json:"pass,omitempty" env:"SEMAPHORE_DB_PASS"`
DbName string `json:"name,omitempty" env:"SEMAPHORE_DB" default:"semaphore"`
Options map[string]string `json:"options,omitempty" env:"SEMAPHORE_DB_OPTIONS"`
Hostname string `json:"host,omitempty" env:"SEMAPHORE_DB_HOST" default:"0.0.0.0"`
Username string `json:"user,omitempty" env:"SEMAPHORE_DB_USER"`
Password string `json:"pass,omitempty" env:"SEMAPHORE_DB_PASS"`
DbName string `json:"name,omitempty" env:"SEMAPHORE_DB" default:"semaphore"`
Options map[string]string `json:"options,omitempty" env:"SEMAPHORE_DB_OPTIONS"`
PostgresIAM bool `json:"postgres_iam,omitempty" env:"SEMAPHORE_DB_POSTGRES_IAM"`
PostgresIAMRegion string `json:"postgres_iam_region,omitempty" env:"SEMAPHORE_DB_POSTGRES_IAM_REGION"`
}

type LdapMappings struct {
Expand Down Expand Up @@ -998,6 +1000,20 @@ func (d *DbConfig) GetConnectionString(includeDbName bool) (connectionString str
}
connectionString += mapToQueryString(options)
case DbDriverPostgres:
if d.PostgresIAM {

if d.PostgresIAMRegion == "" {
return "", fmt.Errorf("Postgres IAM region is not set")
}

authToken, tokenErr := GetRDSAuthToken(d.Hostname, dbUser, d.PostgresIAMRegion)
if tokenErr != nil {
return "", tokenErr
}

dbPass = authToken
}

if includeDbName {
connectionString = fmt.Sprintf(
"postgres://%s:%s@%s/%s",
Expand Down
Loading