1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/vaultRotateSecretId.go
Jordi van Liempt e3935ca088
feat(vault): Vault secret rotation for GH Actions (#4280)
* rotate Vault secret on GH Actions

* test alternative sodium package

* try doing it without libsodium

* disable validity check for testing purposes

* basic unit test

* re-enable secret validity check

* tidy

* tidy parameters

* forgot to update param names in code

* apply review feedback

* improve error logging

* update step metadata

* apply metadata suggestion from review

Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>

* align githubToken param

* Fix secretStore

* Add alias for githubToken

* Move logic to separate file

---------

Co-authored-by: I557621 <jordi.van.liempt@sap.com>
Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
Co-authored-by: Vyacheslav Starostin <vyacheslav.starostin@sap.com>
2023-04-17 08:35:13 +02:00

167 lines
5.5 KiB
Go

package cmd
import (
"context"
"fmt"
"net/http"
"time"
"github.com/hashicorp/vault/api"
"github.com/SAP/jenkins-library/pkg/ado"
"github.com/SAP/jenkins-library/pkg/github"
"github.com/SAP/jenkins-library/pkg/jenkins"
"github.com/SAP/jenkins-library/pkg/vault"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
)
type vaultRotateSecretIDUtils interface {
GetAppRoleSecretIDTtl(secretID, roleName string) (time.Duration, error)
GetAppRoleName() (string, error)
GenerateNewAppRoleSecret(secretID string, roleName string) (string, error)
UpdateSecretInStore(config *vaultRotateSecretIdOptions, secretID string) error
GetConfig() *vaultRotateSecretIdOptions
}
type vaultRotateSecretIDUtilsBundle struct {
*vault.Client
config *vaultRotateSecretIdOptions
updateFunc func(config *vaultRotateSecretIdOptions, secretID string) error
}
func (v vaultRotateSecretIDUtilsBundle) GetConfig() *vaultRotateSecretIdOptions {
return v.config
}
func (v vaultRotateSecretIDUtilsBundle) UpdateSecretInStore(config *vaultRotateSecretIdOptions, secretID string) error {
return v.updateFunc(config, secretID)
}
func vaultRotateSecretId(config vaultRotateSecretIdOptions, telemetryData *telemetry.CustomData) {
vaultConfig := &vault.Config{
Config: &api.Config{
Address: config.VaultServerURL,
},
Namespace: config.VaultNamespace,
}
client, err := vault.NewClientWithAppRole(vaultConfig, GeneralConfig.VaultRoleID, GeneralConfig.VaultRoleSecretID)
if err != nil {
log.Entry().WithError(err).Fatal("could not create Vault client")
}
defer client.MustRevokeToken()
utils := vaultRotateSecretIDUtilsBundle{
Client: &client,
config: &config,
updateFunc: writeVaultSecretIDToStore,
}
err = runVaultRotateSecretID(utils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runVaultRotateSecretID(utils vaultRotateSecretIDUtils) error {
config := utils.GetConfig()
roleName, err := utils.GetAppRoleName()
if err != nil {
log.Entry().WithError(err).Warn("Could not fetch Vault AppRole role name from Vault. Secret ID rotation failed!")
return nil
}
ttl, err := utils.GetAppRoleSecretIDTtl(GeneralConfig.VaultRoleSecretID, roleName)
if err != nil {
log.Entry().WithError(err).Warn("Could not fetch secret ID TTL. Secret ID rotation failed!")
return nil
}
log.Entry().Debugf("Your secret ID is about to expire in %.0f", ttl.Round(time.Hour*24).Hours()/24)
if ttl > time.Duration(config.DaysBeforeExpiry)*24*time.Hour {
return nil
}
newSecretID, err := utils.GenerateNewAppRoleSecret(GeneralConfig.VaultRoleSecretID, roleName)
if err != nil || newSecretID == "" {
log.Entry().WithError(err).Warn("Generating a new secret ID failed. Secret ID rotation faield!")
return nil
}
if err = utils.UpdateSecretInStore(config, newSecretID); err != nil {
log.Entry().WithError(err).Warnf("Could not write secret back to secret store %s", config.SecretStore)
return err
}
log.Entry().Infof("Secret has been successfully updated in secret store %s", config.SecretStore)
return nil
}
func writeVaultSecretIDToStore(config *vaultRotateSecretIdOptions, secretID string) error {
switch config.SecretStore {
case "jenkins":
ctx := context.Background()
instance, err := jenkins.Instance(ctx, &http.Client{}, config.JenkinsURL, config.JenkinsUsername, config.JenkinsToken)
if err != nil {
log.Entry().Warn("Could not write secret ID back to Jenkins")
return err
}
credManager := jenkins.NewCredentialsManager(instance)
credential := jenkins.StringCredentials{ID: config.VaultAppRoleSecretTokenCredentialsID, Secret: secretID}
return jenkins.UpdateCredential(ctx, credManager, config.JenkinsCredentialDomain, credential)
case "ado":
adoBuildClient, err := ado.NewBuildClient(config.AdoOrganization, config.AdoPersonalAccessToken, config.AdoProject, config.AdoPipelineID)
if err != nil {
log.Entry().Warn("Could not write secret ID back to Azure DevOps")
return err
}
variables := []ado.Variable{
{
Name: config.VaultAppRoleSecretTokenCredentialsID,
Value: secretID,
IsSecret: true,
},
}
if err := adoBuildClient.UpdateVariables(variables); err != nil {
log.Entry().Warn("Could not write secret ID back to Azure DevOps")
return err
}
case "github":
// Additional info:
// https://github.com/google/go-github/blob/master/example/newreposecretwithxcrypto/main.go
ctx, client, err := github.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{})
if err != nil {
log.Entry().Warnf("Could not write secret ID back to GitHub Actions: GitHub client not created: %v", err)
return err
}
publicKey, _, err := client.Actions.GetRepoPublicKey(ctx, config.Owner, config.Repository)
if err != nil {
log.Entry().Warnf("Could not write secret ID back to GitHub Actions: repository's public key not retrieved: %v", err)
return err
}
encryptedSecret, err := github.CreateEncryptedSecret(config.VaultAppRoleSecretTokenCredentialsID, secretID, publicKey)
if err != nil {
log.Entry().Warnf("Could not write secret ID back to GitHub Actions: secret encryption failed: %v", err)
return err
}
_, err = client.Actions.CreateOrUpdateRepoSecret(ctx, config.Owner, config.Repository, encryptedSecret)
if err != nil {
log.Entry().Warnf("Could not write secret ID back to GitHub Actions: submission to GitHub failed: %v", err)
return err
}
default:
return fmt.Errorf("error: invalid secret store: %s", config.SecretStore)
}
return nil
}