github.com/xgoffin/jenkins-library@v1.154.0/cmd/vaultRotateSecretId.go (about)

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"time"
     8  
     9  	"github.com/hashicorp/vault/api"
    10  
    11  	"github.com/SAP/jenkins-library/pkg/ado"
    12  	"github.com/SAP/jenkins-library/pkg/jenkins"
    13  	"github.com/SAP/jenkins-library/pkg/vault"
    14  
    15  	"github.com/SAP/jenkins-library/pkg/log"
    16  	"github.com/SAP/jenkins-library/pkg/telemetry"
    17  )
    18  
    19  type vaultRotateSecretIDUtils interface {
    20  	GetAppRoleSecretIDTtl(secretID, roleName string) (time.Duration, error)
    21  	GetAppRoleName() (string, error)
    22  	GenerateNewAppRoleSecret(secretID string, roleName string) (string, error)
    23  	UpdateSecretInStore(config *vaultRotateSecretIdOptions, secretID string) error
    24  	GetConfig() *vaultRotateSecretIdOptions
    25  }
    26  
    27  type vaultRotateSecretIDUtilsBundle struct {
    28  	*vault.Client
    29  	config     *vaultRotateSecretIdOptions
    30  	updateFunc func(config *vaultRotateSecretIdOptions, secretID string) error
    31  }
    32  
    33  func (v vaultRotateSecretIDUtilsBundle) GetConfig() *vaultRotateSecretIdOptions {
    34  	return v.config
    35  }
    36  
    37  func (v vaultRotateSecretIDUtilsBundle) UpdateSecretInStore(config *vaultRotateSecretIdOptions, secretID string) error {
    38  	return v.updateFunc(config, secretID)
    39  }
    40  
    41  func vaultRotateSecretId(config vaultRotateSecretIdOptions, telemetryData *telemetry.CustomData) {
    42  
    43  	vaultConfig := &vault.Config{
    44  		Config: &api.Config{
    45  			Address: config.VaultServerURL,
    46  		},
    47  		Namespace: config.VaultNamespace,
    48  	}
    49  	client, err := vault.NewClientWithAppRole(vaultConfig, GeneralConfig.VaultRoleID, GeneralConfig.VaultRoleSecretID)
    50  	if err != nil {
    51  		log.Entry().WithError(err).Fatal("could not create Vault client")
    52  	}
    53  	defer client.MustRevokeToken()
    54  
    55  	utils := vaultRotateSecretIDUtilsBundle{
    56  		Client:     &client,
    57  		config:     &config,
    58  		updateFunc: writeVaultSecretIDToStore,
    59  	}
    60  
    61  	err = runVaultRotateSecretID(utils)
    62  	if err != nil {
    63  		log.Entry().WithError(err).Fatal("step execution failed")
    64  	}
    65  }
    66  
    67  func runVaultRotateSecretID(utils vaultRotateSecretIDUtils) error {
    68  	config := utils.GetConfig()
    69  
    70  	roleName, err := utils.GetAppRoleName()
    71  	if err != nil {
    72  		log.Entry().WithError(err).Warn("Could not fetch Vault AppRole role name from Vault. Secret ID rotation failed!")
    73  		return nil
    74  	}
    75  
    76  	ttl, err := utils.GetAppRoleSecretIDTtl(GeneralConfig.VaultRoleSecretID, roleName)
    77  
    78  	if err != nil {
    79  		log.Entry().WithError(err).Warn("Could not fetch secret ID TTL. Secret ID rotation failed!")
    80  		return nil
    81  	}
    82  
    83  	log.Entry().Debugf("Your secret ID is about to expire in %.0f", ttl.Round(time.Hour*24).Hours()/24)
    84  
    85  	if ttl > time.Duration(config.DaysBeforeExpiry)*24*time.Hour {
    86  		return nil
    87  	}
    88  
    89  	newSecretID, err := utils.GenerateNewAppRoleSecret(GeneralConfig.VaultRoleSecretID, roleName)
    90  
    91  	if err != nil || newSecretID == "" {
    92  		log.Entry().WithError(err).Warn("Generating a new secret ID failed. Secret ID rotation faield!")
    93  		return nil
    94  	}
    95  
    96  	if err = utils.UpdateSecretInStore(config, newSecretID); err != nil {
    97  		log.Entry().WithError(err).Warnf("Could not write secret back to secret store %s", config.SecretStore)
    98  		return err
    99  	}
   100  	log.Entry().Infof("Secret has been successfully updated in secret store %s", config.SecretStore)
   101  	return nil
   102  
   103  }
   104  
   105  func writeVaultSecretIDToStore(config *vaultRotateSecretIdOptions, secretID string) error {
   106  	switch config.SecretStore {
   107  	case "jenkins":
   108  		ctx := context.Background()
   109  		instance, err := jenkins.Instance(ctx, &http.Client{}, config.JenkinsURL, config.JenkinsUsername, config.JenkinsToken)
   110  		if err != nil {
   111  			log.Entry().Warn("Could not write secret ID back to Jenkins")
   112  			return err
   113  		}
   114  		credManager := jenkins.NewCredentialsManager(instance)
   115  		credential := jenkins.StringCredentials{ID: config.VaultAppRoleSecretTokenCredentialsID, Secret: secretID}
   116  		return jenkins.UpdateCredential(ctx, credManager, config.JenkinsCredentialDomain, credential)
   117  	case "ado":
   118  		adoBuildClient, err := ado.NewBuildClient(config.AdoOrganization, config.AdoPersonalAccessToken, config.AdoProject, config.AdoPipelineID)
   119  		if err != nil {
   120  			log.Entry().Warn("Could not write secret ID back to Azure DevOps")
   121  			return err
   122  		}
   123  		variables := []ado.Variable{
   124  			{
   125  				Name:     config.VaultAppRoleSecretTokenCredentialsID,
   126  				Value:    secretID,
   127  				IsSecret: true,
   128  			},
   129  		}
   130  		if err := adoBuildClient.UpdateVariables(variables); err != nil {
   131  			log.Entry().Warn("Could not write secret ID back to Azure DevOps")
   132  			return err
   133  		}
   134  	default:
   135  		return fmt.Errorf("error: invalid secret store: %s", config.SecretStore)
   136  	}
   137  	return nil
   138  }