From d40d78ca98d851a0c748e9914a3c0ecb7d8d54c1 Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Sun, 31 Mar 2024 20:30:15 +0900 Subject: [PATCH] create Idp --- go.mod | 9 +-- go.sum | 18 +++--- internal/client/aws.go | 35 ++++++++++++ internal/controller/irsasetup_controller.go | 21 +++++-- internal/selfhosted/oidc.go | 15 +++-- internal/selfhosted/oidc/id_provider.go | 55 ++++++------------ ...er_creator.go => id_provider_discovery.go} | 21 ++++--- .../oidc/id_provider_discovery_contents.go | 57 +++++++++++++++++++ 8 files changed, 159 insertions(+), 72 deletions(-) rename internal/selfhosted/oidc/{id_provider_creator.go => id_provider_discovery.go} (66%) create mode 100644 internal/selfhosted/oidc/id_provider_discovery_contents.go diff --git a/go.mod b/go.mod index eb6278a..5331728 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.21 toolchain go1.21.8 require ( - github.com/aws/aws-sdk-go-v2 v1.26.0 + github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.9 + github.com/aws/aws-sdk-go-v2/service/iam v1.31.4 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 github.com/go-jose/go-jose/v4 v4.0.1 github.com/onsi/ginkgo/v2 v2.17.1 @@ -21,8 +22,8 @@ require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.9 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect @@ -32,7 +33,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 // indirect - github.com/aws/smithy-go v1.20.1 // indirect + github.com/aws/smithy-go v1.20.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index a26fa63..fc3cbfd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= -github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo= github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg= @@ -8,14 +8,16 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMN github.com/aws/aws-sdk-go-v2/credentials v1.17.9/go.mod h1:446YhIdmSV0Jf/SLafGZalQo+xr2iw7/fzXGDPTU1yQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 h1:af5YzcLf80tv4Em4jWVD75lpnOHSBkPUZxZfGkrI3HI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= 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/internal/v4a v1.3.4 h1:SIkD6T4zGQ+1YIit22wi37CGNkrE7mXV1vNA5VpI3TI= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4/go.mod h1:XfeqbsG0HNedNs0GT+ju4Bs+pFAwsrlzcRdMvdNVf5s= +github.com/aws/aws-sdk-go-v2/service/iam v1.31.4 h1:eVm30ZIDv//r6Aogat9I88b5YX1xASSLcEDqHYRPVl0= +github.com/aws/aws-sdk-go-v2/service/iam v1.31.4/go.mod h1:aXWImQV0uTW35LM0A/T4wEg6R1/ReXUu4SM6/lUHYK0= 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/checksum v1.3.6 h1:NkHCgg0Ck86c5PTOzBZ0JRccI51suJDg5lgFtxBu1ek= @@ -32,8 +34,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1A github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs= github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg= github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0= -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/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= diff --git a/internal/client/aws.go b/internal/client/aws.go index 914b6ab..eaaddce 100644 --- a/internal/client/aws.go +++ b/internal/client/aws.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/s3" ) @@ -24,6 +25,12 @@ func NewAwsClient(ctx context.Context, region string) (*AwsConfig, error) { return &AwsConfig{config: cfg}, nil } +func (a *AwsConfig) IamCient() *AwsIamClient { + return &AwsIamClient{ + iam.NewFromConfig(a.config), + } +} + func (a *AwsConfig) S3Cient(bucketName string) *AwsS3Client { return &AwsS3Client{ bucketName, @@ -51,3 +58,31 @@ func (a *AwsS3Client) CreateBucket(ctx context.Context) error { }) return err } + +func (a *AwsS3Client) BucketName() string { + return a.bucketName +} + +func (a *AwsS3Client) Region() string { + return a.client.Options().Region +} + +type AwsIamClient struct { + client *iam.Client +} + +func (a *AwsIamClient) CreateOIDCProvider(ctx context.Context, providerUrl string) (string, error) { + result, err := a.client.CreateOpenIDConnectProvider(ctx, &iam.CreateOpenIDConnectProviderInput{ + Url: &providerUrl, + ClientIDList: []string{"sts.amazonaws.com"}, + ThumbprintList: []string{}, + }) + if err != nil { + return "", err + } + return *result.OpenIDConnectProviderArn, nil +} + +func (a *AwsIamClient) Region() string { + return a.client.Options().Region +} diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index e05b341..3486ed3 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -58,8 +58,9 @@ func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } func (r *IRSASetupReconciler) reconcile(ctx context.Context) error { - var idp selfhosted.OIDCIdProvider - var oidcCreator selfhosted.OIDCIdPCreator + var discoveryContents selfhosted.OIDCIdPDiscoveryContents + var discovery selfhosted.OIDCIdPDiscovery + var idp selfhosted.OIDCIdP keyPair, err := selfhosted.CreateKeyPair() if err != nil { return err @@ -68,20 +69,28 @@ func (r *IRSASetupReconciler) reconcile(ctx context.Context) error { if err != nil { return err } - idp = oidc.NewIdProvider(jwk, "issuerHostPath", "keys.json") + discoveryContents = oidc.NewIdPDiscoveryContents(jwk, "issuerHostPath", "keys.json") awsConfig, err := awsclient.NewAwsClient(ctx, "ap-northeast-1") if err != nil { return err } - oidcCreator, err = oidc.NewS3IdPCreator(awsConfig, "my-bucket-name") + discovery, err = oidc.NewS3IdPDiscovery(awsConfig, "my-bucket-name") if err != nil { return err } - err = oidcCreator.CreateStorage() + err = discovery.CreateStorage() if err != nil { return err } - err = oidcCreator.Upload(ctx, idp) + err = discovery.Upload(ctx, discoveryContents) + if err != nil { + return err + } + idp, err = oidc.NewAwsIdP(awsConfig, discovery) + if err != nil { + return err + } + _, err = idp.Create(ctx) if err != nil { return err } diff --git a/internal/selfhosted/oidc.go b/internal/selfhosted/oidc.go index 59099a1..9a4ee6e 100644 --- a/internal/selfhosted/oidc.go +++ b/internal/selfhosted/oidc.go @@ -2,13 +2,20 @@ package selfhosted import "context" -type OIDCIdProvider interface { +type OIDCIdPDiscoveryContents interface { Discovery() ([]byte, error) JWK() ([]byte, error) + JWKsFileName() string +} + +type OIDCIdPDiscovery interface { + CreateStorage() error + Upload(ctx context.Context, o OIDCIdPDiscoveryContents) error Endpoint() string } -type OIDCIdPCreator interface { - CreateStorage() error - Upload(ctx context.Context, o OIDCIdProvider) error +type OIDCIdP interface { + Create(ctx context.Context) (string, error) + IsUpdate() (bool, error) + Update(ctx context.Context) error } diff --git a/internal/selfhosted/oidc/id_provider.go b/internal/selfhosted/oidc/id_provider.go index 1795c6f..122ba95 100644 --- a/internal/selfhosted/oidc/id_provider.go +++ b/internal/selfhosted/oidc/id_provider.go @@ -1,57 +1,34 @@ package oidc import ( - "encoding/json" - "fmt" + "context" + "github.com/kkb0318/irsa-manager/internal/client" "github.com/kkb0318/irsa-manager/internal/selfhosted" ) -type IdProvider struct { - jwk *selfhosted.JWK - issuerHostPath string - jwksFileName string +type AwsIdP struct { + iamClient *client.AwsIamClient + discovery selfhosted.OIDCIdPDiscovery } -type OIDCDiscoveryConfiguration struct { - Issuer string `json:"issuer"` - JWKSURI string `json:"jwks_uri"` - AuthorizationEndpoint string `json:"authorization_endpoint"` - ResponseTypesSupported []string `json:"response_types_supported"` - SubjectTypesSupported []string `json:"subject_types_supported"` - IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` - ClaimsSupported []string `json:"claims_supported"` +func NewAwsIdP(awsConfig *client.AwsConfig, discovery selfhosted.OIDCIdPDiscovery) (*AwsIdP, error) { + iamClient := awsConfig.IamCient() + return &AwsIdP{iamClient, discovery}, nil } -func (p *IdProvider) Discovery() ([]byte, error) { - oidcConfig := OIDCDiscoveryConfiguration{ - Issuer: fmt.Sprintf("https://%s/", p.issuerHostPath), - JWKSURI: fmt.Sprintf("https://%s/%s", p.issuerHostPath, p.jwksFileName), - AuthorizationEndpoint: "urn:kubernetes:programmatic_authorization", - ResponseTypesSupported: []string{"id_token"}, - SubjectTypesSupported: []string{"public"}, - IDTokenSigningAlgValuesSupported: []string{"RS256"}, - ClaimsSupported: []string{"sub", "iss"}, - } - jsonData, err := json.MarshalIndent(oidcConfig, "", " ") +func (a *AwsIdP) Create(ctx context.Context) (string, error) { + arn, err := a.iamClient.CreateOIDCProvider(ctx, a.discovery.Endpoint()) if err != nil { - return nil, err + return "", err } - return jsonData, nil + return arn, nil } -func (p *IdProvider) JWK() ([]byte, error) { - jsonData, err := json.MarshalIndent(p.jwk.GetKeys(), "", " ") - if err != nil { - return nil, err - } - return jsonData, nil +func (a *AwsIdP) Update(ctx context.Context) error { + return nil } -func (p *IdProvider) Endpoint() string { - return "" -} - -func NewIdProvider(jwk *selfhosted.JWK, issuerHostPath, jwksFileName string) *IdProvider { - return &IdProvider{jwk, issuerHostPath, jwksFileName} +func (a *AwsIdP) IsUpdate() (bool, error) { + return false, nil } diff --git a/internal/selfhosted/oidc/id_provider_creator.go b/internal/selfhosted/oidc/id_provider_discovery.go similarity index 66% rename from internal/selfhosted/oidc/id_provider_creator.go rename to internal/selfhosted/oidc/id_provider_discovery.go index 3815b07..d634284 100644 --- a/internal/selfhosted/oidc/id_provider_creator.go +++ b/internal/selfhosted/oidc/id_provider_discovery.go @@ -10,19 +10,19 @@ import ( const CONFIGURATION_PATH = ".well-known/openid-configuration" -type S3IdPCreator struct { +type S3IdPDiscovery struct { s3Client *client.AwsS3Client } -// NewS3IdPCreator initializes a new instance of S3IdPCreator with the specified AWS region and bucket name. +// NewS3IdPDiscovery initializes a new instance of S3IdPCreator with the specified AWS region and bucket name. // This function attempts to create an AWS client configured for the specified region. -func NewS3IdPCreator(awsConfig *client.AwsConfig, bucketName string) (*S3IdPCreator, error) { +func NewS3IdPDiscovery(awsConfig *client.AwsConfig, bucketName string) (*S3IdPDiscovery, error) { s3Client := awsConfig.S3Cient(bucketName) - return &S3IdPCreator{s3Client}, nil + return &S3IdPDiscovery{s3Client}, nil } // CreateStorage creates an S3 bucket -func (s *S3IdPCreator) CreateStorage() error { +func (s *S3IdPDiscovery) CreateStorage() error { err := s.s3Client.CreateBucket(context.TODO()) if err != nil { return fmt.Errorf("unable to create bucket, %w", err) @@ -32,7 +32,7 @@ func (s *S3IdPCreator) CreateStorage() error { // Upload uploads the OIDC provider's discovery configuration and JSON Web Key Set (JWKS) to the specified AWS S3 bucket. // This method is responsible for uploading the necessary OIDC configuration files to S3, making them accessible for OIDC clients. -func (s *S3IdPCreator) Upload(ctx context.Context, o selfhosted.OIDCIdProvider) error { +func (s *S3IdPDiscovery) Upload(ctx context.Context, o selfhosted.OIDCIdPDiscoveryContents) error { discovery, err := o.Discovery() if err != nil { return nil @@ -51,7 +51,7 @@ func (s *S3IdPCreator) Upload(ctx context.Context, o selfhosted.OIDCIdProvider) return nil } err = s.s3Client.PutObject(ctx, - "keys.json", + o.JWKsFileName(), jwk, ) if err != nil { @@ -60,9 +60,8 @@ func (s *S3IdPCreator) Upload(ctx context.Context, o selfhosted.OIDCIdProvider) return nil } -// issuerHostPath constructs the URL path for the OIDC issuer based on the provided AWS region and bucket name. +// Endpoint constructs the URL path for the OIDC issuer based on the provided AWS region and bucket name. // This utility function generates the expected host path for accessing the OIDC configuration stored in an S3 bucket. -func issuerHostPath(region, bucketName string) string { - hostName := fmt.Sprintf("s3-%s.amazonaws.com", region) - return fmt.Sprintf("%s/%s", hostName, bucketName) +func (p *S3IdPDiscovery) Endpoint() string { + return fmt.Sprintf("s3-%s.amazonaws.com/%s", p.s3Client.Region(), p.s3Client.BucketName()) } diff --git a/internal/selfhosted/oidc/id_provider_discovery_contents.go b/internal/selfhosted/oidc/id_provider_discovery_contents.go new file mode 100644 index 0000000..bf89e4a --- /dev/null +++ b/internal/selfhosted/oidc/id_provider_discovery_contents.go @@ -0,0 +1,57 @@ +package oidc + +import ( + "encoding/json" + "fmt" + + "github.com/kkb0318/irsa-manager/internal/selfhosted" +) + +type IdPDiscoveryContents struct { + jwk *selfhosted.JWK + issuerHostPath string + jwksFileName string +} + +type oidcDiscoveryConfiguration struct { + Issuer string `json:"issuer"` + JWKSURI string `json:"jwks_uri"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + ResponseTypesSupported []string `json:"response_types_supported"` + SubjectTypesSupported []string `json:"subject_types_supported"` + IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` + ClaimsSupported []string `json:"claims_supported"` +} + +func (p *IdPDiscoveryContents) Discovery() ([]byte, error) { + oidcConfig := oidcDiscoveryConfiguration{ + Issuer: fmt.Sprintf("https://%s/", p.issuerHostPath), + JWKSURI: fmt.Sprintf("https://%s/%s", p.issuerHostPath, p.jwksFileName), + AuthorizationEndpoint: "urn:kubernetes:programmatic_authorization", + ResponseTypesSupported: []string{"id_token"}, + SubjectTypesSupported: []string{"public"}, + IDTokenSigningAlgValuesSupported: []string{"RS256"}, + ClaimsSupported: []string{"sub", "iss"}, + } + jsonData, err := json.MarshalIndent(oidcConfig, "", " ") + if err != nil { + return nil, err + } + return jsonData, nil +} + +func (p *IdPDiscoveryContents) JWK() ([]byte, error) { + jsonData, err := json.MarshalIndent(p.jwk.GetKeys(), "", " ") + if err != nil { + return nil, err + } + return jsonData, nil +} + +func (p *IdPDiscoveryContents) JWKsFileName() string { + return p.jwksFileName +} + +func NewIdPDiscoveryContents(jwk *selfhosted.JWK, issuerHostPath, jwksFileName string) *IdPDiscoveryContents { + return &IdPDiscoveryContents{jwk, issuerHostPath, jwksFileName} +}