Skip to content
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

Add AWS secretsmanager location #793

Merged
merged 4 commits into from
Jun 29, 2023
Merged
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
99 changes: 50 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
# Cloud-Key-Rotator

[![CircleCI](https://circleci.com/gh/ovotech/cloud-key-rotator/tree/master.svg?style=svg)](https://circleci.com/gh/ovotech/cloud-key-rotator/tree/master)

This is a Golang program to assist with the reporting of Service Account key
ages, and rotating said keys once they pass a specific age threshold.

The tool can update keys held in the following locations:

* Atlas (mongoDB)
* CircleCI env vars
* CircleCI contexts
* Datadog (GCP Integration)
* GCS
* Git
* GitHub Secrets
* GoCd
* K8S (GKE only)
* SSM (AWS Parameter Store)
- Atlas (mongoDB)
- CircleCI env vars
- CircleCI contexts
- Datadog (GCP Integration)
- GCS
- Git
- GitHub Secrets
- GoCd
- K8S (GKE only)
- SSM (AWS Parameter Store)
- AWS SecretsManager

The tool is packaged as an executable file for native invocation, and as a zip
file for deployment as an AWS Lambda.

> :information_source: where possible [OpenID Connect (OIDC)](https://openid.net/connect/)
should be used instead of furnishing/storing long-lived credentials. Using OIDC
will remove the need for running `cloud-key-rotator`.
> should be used instead of furnishing/storing long-lived credentials. Using OIDC
> will remove the need for running `cloud-key-rotator`.

## Install

### From Binary Releases

Darwin, Linux and Windows Binaries can be downloaded from the
[Releases](https://github.com/ovotech/cloud-key-rotator/releases) page.
[Releases](https://github.com/ovotech/cloud-key-rotator/releases) page.

Try it out:

Expand All @@ -54,9 +56,9 @@ HCL.

For native invocation, the file needs to be called "config" (before whatever
extension you're using), and be present either in `/etc/cloud-key-rotator/` or
in the same directory the binary runs in. For AWS Lambda invocation, the config needs
in the same directory the binary runs in. For AWS Lambda invocation, the config needs
to be set as a plaintext secret in the AWS Secrets Manager, using a default key name
of "ckr-config".
of "ckr-config".

### Authentication/Authorisation

Expand All @@ -65,7 +67,7 @@ any key provider that it'll be updating.

Authorisation is handled by the Default Credential Provider Chains for both
[GCP](https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-go) and
[AWS](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default).
[AWS](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default).

### Mode Of Operation

Expand Down Expand Up @@ -95,29 +97,30 @@ ultimately be updated with the new keys that are generated.

Currently, the following locations are supported:

* Atlas (mongoDB)
* CircleCI env vars
* CircleCI contexts
* Datadog (GCP Integration)
* GCS
* Git (files encrypted with [mantle](https://github.com/ovotech/mantle) which
integrates with KMS))
* GitHub Secrets
* GoCd
* K8S (GKE only)
* SSM (AWS Parameter Store)
- Atlas (mongoDB)
- CircleCI env vars
- CircleCI contexts
- Datadog (GCP Integration)
- GCS
- Git (files encrypted with [mantle](https://github.com/ovotech/mantle) which
integrates with KMS))
- GitHub Secrets
- GoCd
- K8S (GKE only)
- SSM (AWS Parameter Store)
- AWS SecretsManager

## Rotation Process

The tool attempts to verify its actions as much as possible and aborts
immediately if it encounters an error. By design, the tool does **not** attempt to
immediately if it encounters an error. By design, the tool does **not** attempt to
handle errors gracefully and continue, since this can lead to a "split-brain effect",
with keys out-of-sync in various locations.

It should be quick to re-run the tool (with new keys being created) once issues
have been resolved. Note that cloud providers usually limit the number of
keys you can have attached to a Service Account at any one time, so it is
worth bearing this in mind when re-running manually after seeing errors.
have been resolved. Note that cloud providers usually limit the number of
keys you can have attached to a Service Account at any one time, so it is
worth bearing this in mind when re-running manually after seeing errors.

Only the first key of a Service Account is handled by `cloud-key-rotator`. If
it handled more than one key, it could lead to complications when updating
Expand Down Expand Up @@ -168,12 +171,13 @@ to a Git repository.
Commits to Git repositories are required to be GPG signed. In order to
achieve this, you need to provide 4 things:

* `Username` of the Git user commits will be made on behalf of, set in config
* `Email` address of Git user, set in config
* `ArmouredKeyRing`, aka GPG private key, stored in `/etc/cloud-key-rotator/akr.asc`
* `Passphrase` to the ArmouredKeyRing
- `Username` of the Git user commits will be made on behalf of, set in config
- `Email` address of Git user, set in config
- `ArmouredKeyRing`, aka GPG private key, stored in `/etc/cloud-key-rotator/akr.asc`
- `Passphrase` to the ArmouredKeyRing

e.g. along with the `akr.asc` file, you should set the following:

```JSON
"AkrPass": "change_me",
"GitName": "git-name",
Expand Down Expand Up @@ -212,16 +216,15 @@ Service Account's email address.
## Rotation Flow

1. Reduce keys to those of service accounts deemed to be valid (e.g. strip out
user accounts if in rotation-mode)
user accounts if in rotation-mode)
2. Filter keys to those deemed to be eligible (e.g. according to filtering rules
configured by the user)
configured by the user)
3. For each eligible key:

* Create new key
* Update key locations
* Verify update has worked (where possible)
* Delete old key

- Create new key
- Update key locations
- Verify update has worked (where possible)
- Delete old key

## Troubleshooting

Expand All @@ -239,7 +242,7 @@ Try and schedule rotations for times that are unlikely to conflict with CI/CD
jobs.

- When trying to create a GCP AppEngine App (a pre-requisite of being able to
create CloudScheduler jobs) I get an error: `Error waiting for App Engine app to
create CloudScheduler jobs) I get an error: `Error waiting for App Engine app to
create: Error code 13, message: AppEngine service account cannot be generated for e~<project_name>`

This happens when the AppEngine default service account has been previously
Expand All @@ -249,7 +252,7 @@ available to try. If that's not possible, GCP support should be able to restore
the service account.

- CloudScheduler fails to invoke the `cloud-key-rotator` CloudFunction, showing
a `PERMISSION DENIED` error in logs
a `PERMISSION DENIED` error in logs

For the CloudScheduler to have permission to invoke CloudFunctions, the Cloud
Scheduler service account must be given the `Cloud Scheduler Service Agent`
Expand All @@ -272,13 +275,11 @@ The user/owner (preferably a bot user) of the CircleCI API key that you pass to
`cloud-key-rotator` must have write access to the GitHub repo that the CircleCI
jobs run from.



## Contributions

Contributions are more than welcome from both internal (to ovotech) and external
contributors.

If you have write access to this repo, create a branch and PR,
otherwise fork and PR. Forked branches will be pushed to this repo by a
reviewer so PR checks can run.
If you have write access to this repo, create a branch and PR,
otherwise fork and PR. Forked branches will be pushed to this repo by a
reviewer so PR checks can run.
1 change: 1 addition & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type KeyLocations struct {
Gocd []location.Gocd
K8s []location.K8s
SSM []location.Ssm
SecretsManager []location.SecretsManager
}

//ProviderServiceAccounts type
Expand Down
70 changes: 70 additions & 0 deletions pkg/location/secretsmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package location

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
awsSm "github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/ovotech/cloud-key-rotator/pkg/cred"
)

// SecretsManager type
type SecretsManager struct {
KeyParamName string
KeyIDParamName string
Region string
ConvertToFile bool
FileType string
}

func (sm SecretsManager) Write(serviceAccountName string, keyWrapper KeyWrapper, creds cred.Credentials) (updated UpdatedLocation, err error) {
provider := keyWrapper.KeyProvider
var key string
var keyEnvVar string
var keyIDEnvVar string
var idValue bool

if keyEnvVar, err = getVarNameFromProvider(provider, sm.KeyParamName, idValue); err != nil {
return
}

if sm.ConvertToFile || provider == "gcp" {
if key, err = getKeyForFileBasedLocation(keyWrapper, sm.FileType); err != nil {
return
}
} else {
key = keyWrapper.Key
idValue = true
if keyIDEnvVar, err = getVarNameFromProvider(provider, sm.KeyIDParamName, idValue); err != nil {
return
}
}
svc := awsSm.New(
session.New(),
aws.NewConfig().
WithRegion(sm.Region).
WithEndpoint(fmt.Sprintf("secretsmanager.%s.amazonaws.com", sm.Region)),
)

if len(keyIDEnvVar) > 0 {
if err = updateSecretsManagerSecret(keyIDEnvVar, keyWrapper.KeyID, *svc); err != nil {
return
}
}
if err = updateSecretsManagerSecret(keyEnvVar, key, *svc); err != nil {
return
}

updated = UpdatedLocation{
LocationType: "secretsmanager",
LocationURI: sm.Region,
LocationIDs: []string{keyIDEnvVar, keyEnvVar}}
return
}

func updateSecretsManagerSecret(paramName, paramValue string, svc awsSm.SecretsManager) (err error) {
input := &awsSm.PutSecretValueInput{SecretId: &paramName, SecretString: &paramValue}
_, err = svc.PutSecretValue(input)
return
}
4 changes: 4 additions & 0 deletions pkg/rotate/rotatekeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ func locationsToUpdate(keyLocation config.KeyLocations) (kws []location.KeyWrite
kws = append(kws, ssm)
}

for _, secretsmanager := range keyLocation.SecretsManager {
kws = append(kws, secretsmanager)
}

if googleAppCredsRequired {
ensureGoogleAppCreds()
}
Expand Down