Skip to content

Commit

Permalink
add: install argo-workflows-cognito
Browse files Browse the repository at this point in the history
  • Loading branch information
aaroniscode committed Jul 30, 2023
1 parent 77d59e3 commit 42fc8ad
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 8 deletions.
1 change: 1 addition & 0 deletions cmd/install/argo.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ func init() {
argoApps = []func() *application.Application{
argo_cd.NewApp,
workflows.NewApp,
workflows.NewAppWithCognito,
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/awslabs/eksdemo
go 1.19

require (
github.com/Masterminds/goutils v1.1.1
github.com/Masterminds/sprig/v3 v3.2.3
github.com/aws/aws-sdk-go-v2 v1.18.1
github.com/aws/aws-sdk-go-v2/config v1.18.12
Expand Down Expand Up @@ -55,7 +56,6 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
Expand Down
202 changes: 202 additions & 0 deletions pkg/application/argo/workflows/argo_workflows_cognito.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package workflows

import (
"errors"
"fmt"
"strings"

"github.com/Masterminds/goutils"
awssdk "github.com/aws/aws-sdk-go-v2/aws"
"github.com/awslabs/eksdemo/pkg/application"
"github.com/awslabs/eksdemo/pkg/aws"
"github.com/awslabs/eksdemo/pkg/cmd"
"github.com/awslabs/eksdemo/pkg/installer"
"github.com/awslabs/eksdemo/pkg/resource"
"github.com/awslabs/eksdemo/pkg/resource/cognito/client"
"github.com/awslabs/eksdemo/pkg/resource/cognito/userpool"
"github.com/awslabs/eksdemo/pkg/template"
)

const appClientName = "argo-workflows"

type CognitoOptions struct {
*Options
UserPoolOptions *userpool.Options

ClientID string
ClientSecret string
OAuthScopes []string
UserPoolID string
}

func NewAppWithCognito() *application.Application {
options, flags := newCognitoOptions()

return &application.Application{
Command: cmd.Command{
Parent: "argo",
Name: "workflows-cognito",
Description: "Workflow engine for Kubernetes using Cognito for authentication",
},

Dependencies: []*resource.Resource{
userpool.NewWithOptions(options.UserPoolOptions),
},

Flags: flags,

Installer: &installer.HelmInstaller{
ChartName: "argo-workflows",
ReleaseName: "argo-workflows-cognito",
RepositoryURL: "https://argoproj.github.io/argo-helm",
ValuesTemplate: &template.TextTemplate{
Template: valuesTemplate + cognitoValuesTemplate,
},
PostRenderKustomize: &template.TextTemplate{
Template: postRenderKustomize,
},
},

Options: options,
}
}

func newCognitoOptions() (options *CognitoOptions, flags cmd.Flags) {
workflowOptions, flags := newOptions()

workflowOptions.AuthMode = "sso"
workflowOptions.IngressOnly = true

options = &CognitoOptions{
Options: workflowOptions,
OAuthScopes: []string{"email"},
UserPoolOptions: &userpool.Options{},
}
return
}

// https://github.com/argoproj/argo-helm/blob/main/charts/argo-workflows/values.yaml
const cognitoValuesTemplate = `
sso:
enabled: true
issuer: https://cognito-idp.{{ .Region }}.amazonaws.com/{{ .UserPoolID }}
clientId:
name: argo-server-sso
key: client-id
clientSecret:
name: argo-server-sso
key: client-secret
redirectUrl: https://{{ .IngressHost }}/oauth2/callback
rbac:
enabled: false
scopes: {{ .OAuthScopes }}
extraObjects:
- apiVersion: v1
kind: Secret
metadata:
name: argo-server-sso
data:
client-id: {{ .ClientID | b64enc }}
client-secret: {{ .ClientSecret | b64enc}}
type: Opaque
`

// Workaround for https://github.com/argoproj/argo-helm/issues/2159
const postRenderKustomize = `---
resources:
- manifest.yaml
patches:
# Add service account permission to the argo server clusterrole
- patch: |-
- op: add
path: /rules/-
value:
apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- get
- list
- watch
target:
group: rbac.authorization.k8s.io
kind: ClusterRole
name: argo-server
version: v1
`

func (o *CognitoOptions) userPoolName() string {
return fmt.Sprintf("%s-%s", o.ClusterName, "argo")
}

func (o *CognitoOptions) PreDependencies(application.Action) error {
o.UserPoolOptions.UserPoolName = o.userPoolName()
return nil
}

func (o *CognitoOptions) PreInstall() error {
if o.DryRun {
fmt.Println("\nPreInstall Dry Run:")
fmt.Println("1. Check if the user pool has a domain configured.")
fmt.Println("2. If no domain is configured, create a Cognito domain.")
fmt.Println("3. Create a new Cognito app client.")
return nil
}

cognitoClient := aws.NewCognitoUserPoolClient()

userPool, err := userpool.NewGetter(cognitoClient).GetUserPoolByName(o.UserPoolOptions.UserPoolName)
if err != nil {
return err
}

o.UserPoolID = awssdk.ToString(userPool.Id)

if userPool.Domain == nil {
fmt.Printf("User Pool %q does not have a domain configured.\n", o.UserPoolOptions.UserPoolName)

rand, err := goutils.CryptoRandomAlphaNumeric(8)
if err != nil {
return fmt.Errorf("failed to generate random string: %w", err)
}

prefix := fmt.Sprintf("eksdemo-argo-%s", strings.ToLower(rand))
fmt.Printf("Creating new cognito domain with prefix %q...", prefix)

_, err = cognitoClient.CreateUserPoolDomain(prefix, o.UserPoolID)
if err != nil {
return err
}
fmt.Println("done")
}

_, err = client.NewGetter(cognitoClient).GetAppClientByName(appClientName, o.UserPoolID)
if err == nil {
return fmt.Errorf("app client %q already exists for user pool %q, please delete it and try again",
appClientName, o.UserPoolID)
}

// Return the error if it's anything except "not found" error
var notFoundErr *resource.NotFoundByNameError
if err != nil && !errors.As(err, &notFoundErr) {
return err
}

fmt.Printf("Creating new app client %q...", appClientName)
appClient, err := cognitoClient.CreateUserPoolClient(
append(o.OAuthScopes, "openid"),
[]string{fmt.Sprintf("https://%s/oauth2/callback", o.IngressHost)},
appClientName,
o.UserPoolID,
)
if err != nil {
return err
}
fmt.Println("done")

o.ClientID = awssdk.ToString(appClient.ClientId)
o.ClientSecret = awssdk.ToString(appClient.ClientSecret)

return nil
}
15 changes: 8 additions & 7 deletions pkg/aws/cognito_userpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ func (c *CognitoUserPoolClient) CreateUserPool(name string) (*types.UserPoolType
// When you create a new user pool client, token revocation is automatically activated.
func (c *CognitoUserPoolClient) CreateUserPoolClient(oauthScopes, callbackUrls []string, clientName, userPoolID string) (*types.UserPoolClientType, error) {
input := cognitoidp.CreateUserPoolClientInput{
AllowedOAuthFlows: []types.OAuthFlowType{types.OAuthFlowTypeCode},
AllowedOAuthScopes: oauthScopes,
CallbackURLs: callbackUrls,
ClientName: aws.String(clientName),
GenerateSecret: true,
SupportedIdentityProviders: []string{"COGNITO"},
UserPoolId: aws.String(userPoolID),
AllowedOAuthFlows: []types.OAuthFlowType{types.OAuthFlowTypeCode},
AllowedOAuthFlowsUserPoolClient: true,
AllowedOAuthScopes: oauthScopes,
CallbackURLs: callbackUrls,
ClientName: aws.String(clientName),
GenerateSecret: true,
SupportedIdentityProviders: []string{"COGNITO"},
UserPoolId: aws.String(userPoolID),
}

result, err := c.Client.CreateUserPoolClient(context.Background(), &input)
Expand Down

0 comments on commit 42fc8ad

Please sign in to comment.