diff --git a/upgrades/0.0.89.rotate-argocd-ssh-key/go.mod b/upgrades/0.0.89.rotate-argocd-ssh-key/go.mod index eae4c73..7f64ed7 100644 --- a/upgrades/0.0.89.rotate-argocd-ssh-key/go.mod +++ b/upgrades/0.0.89.rotate-argocd-ssh-key/go.mod @@ -3,7 +3,9 @@ module github.com/oslokommune/okctl-upgrade/upgrades/0.0.89.rotate-argocd-ssh-ke go 1.16 require ( - github.com/oslokommune/okctl v0.0.88-0.20220207135528-646363d27513 + github.com/google/go-github/v32 v32.1.0 + github.com/oslokommune/okctl v0.0.88-0.20220209135710-bb11f2be6bb1 github.com/spf13/cobra v1.3.0 + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 sigs.k8s.io/yaml v1.3.0 ) diff --git a/upgrades/0.0.89.rotate-argocd-ssh-key/go.sum b/upgrades/0.0.89.rotate-argocd-ssh-key/go.sum index 24090ef..1c636e3 100644 --- a/upgrades/0.0.89.rotate-argocd-ssh-key/go.sum +++ b/upgrades/0.0.89.rotate-argocd-ssh-key/go.sum @@ -147,6 +147,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 h1:VauE2GcJNZFun2Och6tIT2zJZK1v6jxALQDA9BIji/E= +github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5/go.mod h1:gxOHeajFfvGQh/fxlC8oOKBe23xnnJTif00IFFbiT+o= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -407,6 +409,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU= +github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -516,7 +520,6 @@ github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -1163,8 +1166,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= -github.com/oslokommune/okctl v0.0.88-0.20220207135528-646363d27513 h1:wn18IH0XLoBaVGbbNHYHeIyULj4F6v1c2sCFQ+D0qtQ= -github.com/oslokommune/okctl v0.0.88-0.20220207135528-646363d27513/go.mod h1:f2ur1H1pj7OSItcEUiAgflVlV8RngdT2DH4YZdOiYQI= +github.com/oslokommune/okctl v0.0.88-0.20220209135710-bb11f2be6bb1 h1:4Sotc+r2Lc32/sNgqY5txSDYSSML5/FgUIFPi2kQVng= +github.com/oslokommune/okctl v0.0.88-0.20220209135710-bb11f2be6bb1/go.mod h1:Mqfz08R5hAx2etZaqWL9vGXFFQJSuqIsZyrWttEmV9c= github.com/oslokommune/okctl-metrics-service v0.1.8 h1:9zwtBD6p91FZqkk386k0rm1jGQF4rRw7qn6pm4ecnss= github.com/oslokommune/okctl-metrics-service v0.1.8/go.mod h1:yDjdZUm2AzQw/dQDhzcdCbm9KSoOq6NSdUXbOabFUmQ= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1724,6 +1727,7 @@ golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github/github.go b/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github/github.go new file mode 100644 index 0000000..2c75ecc --- /dev/null +++ b/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github/github.go @@ -0,0 +1,126 @@ +// Package github provides a client for interacting with the Github API +package github + +import ( + "context" + "errors" + "fmt" + + "github.com/google/go-github/v32/github" + githubAuth "github.com/oslokommune/okctl/pkg/credentials/github" + "golang.org/x/oauth2" +) + +var ErrNotFound = errors.New("not found") + +// Githuber invokes the github API +type Githuber interface { + GetDeployKeys(org, repository, deployKeyName string) ([]*Key, error) +} + +// Github contains the state for interacting with the github API +type Github struct { + Ctx context.Context + Client *github.Client +} + +func (g *Github) GetDeployKeys(org, repository, deployKeyName string) ([]*Key, error) { + allKeys, err := g.ListDeployKey(org, repository) + if err != nil { + return nil, fmt.Errorf("getting deploy key: %w", err) + } + + var keysWithName []*Key + + for _, key := range allKeys { + if key.GetTitle() == deployKeyName { + keysWithName = append(keysWithName, key) + } + } + + if len(keysWithName) == 0 { + return nil, ErrNotFound + } + + return keysWithName, nil +} + +func (g *Github) ListDeployKey(org, repository string) ([]*Key, error) { + opts := &github.ListOptions{ + Page: 0, + PerPage: 100, + } + + var allKeys []*Key + + for { + keys, response, err := g.Client.Repositories.ListKeys(g.Ctx, org, repository, opts) + if err != nil { + return nil, fmt.Errorf("listing deploy keys: %w", err) + } + + allKeys = append(allKeys, keys...) + + if response.NextPage == 0 { + break + } + + opts.Page = response.NextPage + } + + return allKeys, nil +} + +// Ensure that Github implements Githuber +var _ Githuber = &Github{} + +// Key shadows github.Key +type Key = github.Key + +// New returns an initialised github API client +func New(ctx context.Context, auth githubAuth.Authenticator) (*Github, error) { + credentials, err := auth.Raw() + if err != nil { + return nil, fmt.Errorf("failed to get github credentials: %w", err) + } + + client := github.NewClient( + oauth2.NewClient(ctx, + oauth2.StaticTokenSource( + &oauth2.Token{ + AccessToken: credentials.AccessToken, + }, + ), + ), + ) + + return &Github{ + Ctx: ctx, + Client: client, + }, nil +} + +// DeleteDeployKey removes a read-only deploy key +func (g *Github) DeleteDeployKey(org, repository string, identifier int64) error { + _, err := g.Client.Repositories.DeleteKey(g.Ctx, org, repository, identifier) + if err != nil { + return fmt.Errorf("deleting deploy key: %w", err) + } + + return nil +} + +// BoolPtr returns a pointer to the bool +func BoolPtr(v bool) *bool { + return &v +} + +// StringPtr returns a pointer to the string +func StringPtr(v string) *string { + return &v +} + +// Int64Ptr returns a pointer to the int64 +func Int64Ptr(v int64) *int64 { + return &v +} diff --git a/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github/helper.go b/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github/helper.go new file mode 100644 index 0000000..6676fae --- /dev/null +++ b/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github/helper.go @@ -0,0 +1,13 @@ +package github + +import "fmt" + +// GithubDeployKeySecretName returns the full name of a deploy key secret in SSM Parameter store +// +// The imported version of Okctl contains a bug in parameter_aws.go, where it should prefix secret names with /okctl/clusterName, +// but doesn't. +// +// See: https://trello.com/c/X4J8bzu1/554-deleting-secrets-in-paremeter-store-doesnt-work +func GithubDeployKeySecretName(clusterName, org, repo string) string { + return fmt.Sprintf("okctl/github/deploykeys/%s/%s/privatekey", org, repo) +} diff --git a/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/rotatesshkey/api.go b/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/rotatesshkey/api.go index 0447c4c..660b883 100644 --- a/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/rotatesshkey/api.go +++ b/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/rotatesshkey/api.go @@ -2,8 +2,10 @@ package rotatesshkey import ( "context" + "errors" "fmt" "github.com/oslokommune/okctl-upgrade/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/cmdflags" + upgradeGithub "github.com/oslokommune/okctl-upgrade/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/github" "github.com/oslokommune/okctl-upgrade/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/logger" "github.com/oslokommune/okctl-upgrade/upgrades/0.0.89.rotate-argocd-ssh-key/pkg/okctlimport" "github.com/oslokommune/okctl/pkg/api" @@ -14,53 +16,110 @@ import ( clientDirectAPI "github.com/oslokommune/okctl/pkg/client/core/api/direct" "github.com/oslokommune/okctl/pkg/config/constant" "github.com/oslokommune/okctl/pkg/github" + "github.com/oslokommune/okctl/pkg/keypair" ) // SSHKeyRotater is a sample okctl component type SSHKeyRotater struct { - flags cmdflags.Flags - log logger.Logger - clusterID api.ID - declaration *v1alpha1.Cluster - githubState client.GithubState - parameterService api.ParameterService - githubClient *github.Github - githubAPI client.GithubAPI + flags cmdflags.Flags + log logger.Logger + clusterID api.ID + declaration *v1alpha1.Cluster + githubState client.GithubState + parameterService api.ParameterService + githubDeployKeyGetter *upgradeGithub.Github + githubClient *github.Github + githubAPI client.GithubAPI } +// TOdo hvorfor tryner ikke oppgradering om state mangler + // Upgrade upgrades the component func (r SSHKeyRotater) Upgrade() error { - r.log.Info("Rotating ArgoCD SSH keys using format ed25519") + r.log.Info("Rotating ArgoCD SSH keys") - if r.flags.DryRun { - r.log.Info("Simulating some stuff") - return nil + err := r.removeDeployKeysIfExists() + if err != nil { + return fmt.Errorf("removing deploy key: %w", err) } - fullName := fmt.Sprintf("%s/%s", r.declaration.Github.Organisation, r.declaration.Github.Repository) - - githubRepo, err := r.githubState.GetGithubRepository(fullName) + err = r.createDeployKey() if err != nil { - return fmt.Errorf("getting github repository: %w", err) + return fmt.Errorf("creating deploy key: %w", err) } - // Remove old deploy key - err = r.githubAPI.DeleteRepositoryDeployKey(client.DeleteGithubDeployKeyOpts{ - ID: r.clusterID, - Organisation: r.declaration.Github.Organisation, - Repository: r.declaration.Github.Repository, - Identifier: githubRepo.DeployKey.Identifier, + r.log.Info("Rotating ArgoCD SSH keys done!") + + return nil +} + +func (r SSHKeyRotater) removeDeployKeysIfExists() error { + r.log.Info("Deleting parameter store secret") + + key, _ := keypair.Generate() + fmt.Println(string(key.PrivateKey)) + fmt.Println(string(key.PublicKey)) + + err := r.parameterService.DeleteSecret(context.Background(), api.DeleteSecretOpts{ + Name: upgradeGithub.GithubDeployKeySecretName( + r.clusterID.ClusterName, r.declaration.Github.Organisation, r.declaration.Github.Repository), }) if err != nil { - return err + return fmt.Errorf("deleting parameter store secret: %w", err) + } + + // Because the github deploy key ID in our state can be wrong, we cannot use okctl's implemented functionality to remove the + // github deploy key, because it requires the correct ID. We have to get the correct ID by getting the deploy key by expected + // name. + existingKeysToCleanup, err := r.getGithubDeployKeys() + if err != nil && !errors.Is(err, upgradeGithub.ErrNotFound) { + return fmt.Errorf("getting github deploy key identifier: %w", err) + } + + r.log.Infof("Found %d old deploy keys to remove from GitHub\n", len(existingKeysToCleanup)) + + for _, key := range existingKeysToCleanup { + r.log.Infof("Deleting old deploy key %s (id: %d)\n", key.GetTitle(), key.GetID()) + + if r.flags.DryRun { + continue + } + + err = r.githubClient.DeleteDeployKey( + r.declaration.Github.Organisation, r.declaration.Github.Repository, key.GetID()) + if err != nil && !errors.Is(err, upgradeGithub.ErrNotFound) { + return fmt.Errorf("deleting deploy key in GitHub: %w", err) + } } - err = r.githubState.RemoveGithubRepository(fullName) + return nil +} + +func (r SSHKeyRotater) getGithubDeployKeys() ([]*upgradeGithub.Key, error) { + deployKeyTitle := fmt.Sprintf("okctl-iac-%s", r.declaration.Metadata.Name) + + keys, err := r.githubDeployKeyGetter.GetDeployKeys( + r.declaration.Github.Organisation, r.declaration.Github.Repository, deployKeyTitle) if err != nil { - return fmt.Errorf("removing github repository key: %w", err) + return nil, fmt.Errorf("getting deploy key '%s': %w", deployKeyTitle, err) + } + + for _, key := range keys { + if key.GetID() == 0 { + return nil, fmt.Errorf("received deploy key '%s' without ID", key.GetTitle()) + } + } + + return keys, nil +} + +func (r SSHKeyRotater) createDeployKey() error { + r.log.Info("Creating deploy key") + + if r.flags.DryRun { + return nil } - // Create new deploy key deployKey, err := r.githubAPI.CreateRepositoryDeployKey(client.CreateGithubDeployKeyOpts{ ID: r.clusterID, Organisation: r.declaration.Github.Organisation, @@ -71,6 +130,8 @@ func (r SSHKeyRotater) Upgrade() error { return fmt.Errorf("creating repository deploy key: %w", err) } + fullName := fmt.Sprintf("%s/%s", r.declaration.Github.Organisation, r.declaration.Github.Repository) + repo := &client.GithubRepository{ ID: r.clusterID, Organisation: r.declaration.Github.Organisation, @@ -80,13 +141,12 @@ func (r SSHKeyRotater) Upgrade() error { DeployKey: deployKey, } + r.log.Info("Updating GitHub state") + err = r.githubState.SaveGithubRepository(repo) if err != nil { return fmt.Errorf("saving github repository: %w", err) } - - r.log.Info("Rotating ArgoCD SSH keys done!") - return nil } @@ -102,6 +162,11 @@ func New(logger logger.Logger, flags cmdflags.Flags) (SSHKeyRotater, error) { awsProvider.NewParameterCloudProvider(o.CloudProvider), ) + githubDeployKeyGetter, err := upgradeGithub.New(context.Background(), o.CredentialsProvider.Github()) + if err != nil { + return SSHKeyRotater{}, fmt.Errorf("creating github deploy key client: %w", err) + } + githubClient, err := github.New(context.Background(), o.CredentialsProvider.Github()) if err != nil { return SSHKeyRotater{}, fmt.Errorf("creating github client: %w", err) @@ -113,13 +178,14 @@ func New(logger logger.Logger, flags cmdflags.Flags) (SSHKeyRotater, error) { ) return SSHKeyRotater{ - log: logger, - flags: flags, - declaration: o.Declaration, - clusterID: okctlimport.GetClusterID(o), - githubState: state.Github, - parameterService: parameterService, - githubClient: githubClient, - githubAPI: githubAPI, + log: logger, + flags: flags, + declaration: o.Declaration, + clusterID: okctlimport.GetClusterID(o), + githubState: state.Github, + parameterService: parameterService, + githubDeployKeyGetter: githubDeployKeyGetter, + githubClient: githubClient, + githubAPI: githubAPI, }, nil }