pkg/tasks/to/gha.go (105 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package to import ( "context" "crypto/rand" "encoding/base64" "errors" "fmt" "os" "github.com/google/go-github/v42/github" "github.com/elastic/harp/pkg/bundle" "github.com/elastic/harp/pkg/tasks" "golang.org/x/crypto/nacl/box" "golang.org/x/oauth2" ) type GithubActionTask struct { _ struct{} ContainerReader tasks.ReaderProvider Owner string Repository string SecretFilter string } func (t *GithubActionTask) Run(ctx context.Context) error { // Create the reader reader, err := t.ContainerReader(ctx) if err != nil { return fmt.Errorf("unable to open input bundle reader: %w", err) } // Extract bundle from container b, err := bundle.FromContainerReader(reader) if err != nil { return fmt.Errorf("unable to load bundle: %w", err) } // Prepae github API client client, err := t.prepareClient(ctx) if err != nil { return fmt.Errorf("unable to prepare github api client: %w", err) } // Retrieve repository public key keyID, boxKey, err := t.getRepositoryKey(ctx, client) if err != nil { return fmt.Errorf("unable to retieve repository public key: %w", err) } // Requests to send to github githubSecrets := []*github.EncryptedSecret{} // Iterate over packages for _, p := range b.Packages { // Ignore nil secret chain if p.Secrets == nil { continue } // Get secrets secretMap, err := bundle.AsSecretMap(p) if err != nil { return fmt.Errorf("unable to retrieve secrets from '%s' package: %w", p.Name, err) } // Filter secrets map using given filter glob. filteredSecrets := secretMap.Glob(t.SecretFilter) // Iterate over secrets for secretKey, value := range filteredSecrets { var secretBytes []byte // Pack the secret value switch v := value.(type) { case string: secretBytes = []byte(v) case []byte: secretBytes = v default: return fmt.Errorf("can't process secret type %T", value) } // The secret is encrypted with box.SealAnonymous using the repo's decoded public key. encryptedBytes, err := box.SealAnonymous([]byte{}, secretBytes, boxKey, rand.Reader) if err != nil { return fmt.Errorf("unable to encrypt the secret payload: %w", err) } // Prepare the request githubSecrets = append(githubSecrets, &github.EncryptedSecret{ Name: secretKey, KeyID: keyID, EncryptedValue: base64.StdEncoding.EncodeToString(encryptedBytes), }) } } // Publish all secrets for _, gs := range githubSecrets { // Create or update the secret value. if _, err := client.Actions.CreateOrUpdateRepoSecret(ctx, t.Owner, t.Repository, gs); err != nil { return fmt.Errorf("unable to publish secret to github: %w", err) } } // No error return nil } func (t *GithubActionTask) prepareClient(ctx context.Context) (*github.Client, error) { // Retrieve github token githubToken := os.Getenv("GITHUB_TOKEN") if githubToken == "" { return nil, errors.New("GITHUB_TOKEN environment variable must be set") } // Create an authenticated transport tc := oauth2.NewClient( ctx, oauth2.StaticTokenSource( &oauth2.Token{ AccessToken: githubToken, }, ), ) // Create github API client client := github.NewClient(tc) // No error return client, nil } func (t *GithubActionTask) getRepositoryKey(ctx context.Context, client *github.Client) (keyID string, publicKey *[32]byte, err error) { // Retrieve repository public key pub, _, err := client.Actions.GetRepoPublicKey(ctx, t.Owner, t.Repository) if err != nil { return "", nil, fmt.Errorf("unable to retrieve repository public key for secret encryption: %w", err) } // Decode public key. decodedPublicKey, err := base64.StdEncoding.DecodeString(pub.GetKey()) if err != nil { return pub.GetKeyID(), nil, fmt.Errorf("unable to decode public key from github: %w", err) } // The decode key is converted into a fixed size byte array. var boxKey [32]byte // The secret value is converted into a slice of bytes. copy(boxKey[:], decodedPublicKey) // No error return pub.GetKeyID(), &boxKey, nil }