Skip to content

Commit

Permalink
add generic secret retrieval method
Browse files Browse the repository at this point in the history
  • Loading branch information
malt3 committed Feb 22, 2025
1 parent 41b5469 commit 7284c82
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
15 changes: 15 additions & 0 deletions authenticate/internal/lookupchain/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
load("@rules_go//go:def.bzl", "go_library")

go_library(
name = "lookupchain",
srcs = ["lookupchain.go"],
importpath = "github.com/tweag/credential-helper/authenticate/internal/lookupchain",
visibility = ["//authenticate:__subpackages__"],
deps = ["@com_github_zalando_go_keyring//:go-keyring"],
)

filegroup(
name = "all_files",
srcs = glob(["*"]),
visibility = ["//:__subpackages__"],
)
186 changes: 186 additions & 0 deletions authenticate/internal/lookupchain/lookupchain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package lookupchain

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"strings"

keyring "github.com/zalando/go-keyring"
)

const (
SourceEnv = "env"
SourceKeyring = "keyring"
)

type LookupChain struct {
config Config
}

func New(config Config) *LookupChain {
return &LookupChain{config: config}
}

// Lookup looks up a binding in the chain.
// It returns the first value found, or an error.
func (c *LookupChain) Lookup(binding string) (string, error) {
if len(c.config) == 0 {
return "", fmt.Errorf("no sources configured to look up binding %q", binding)
}
for _, entry := range c.config {
source, err := c.sourceFor(entry)
if err != nil {
return "", fmt.Errorf("looking up binding %q: %w", binding, err)
}
result, err := source.Lookup(binding)
if err == nil {
return result, nil
}
if errors.Is(err, notFoundError) {
continue
}
return "", fmt.Errorf("looking up binding %q: %w", binding, err)
}
sourceNames := make([]string, len(c.config))
for i, entry := range c.config {
sourceNames[i] = entry.Source
}
return "", fmt.Errorf("no value found for binding %q after querying %v", binding, strings.Join(sourceNames, ", "))
}

func (c *LookupChain) sourceFor(entry ConfigEntry) (Source, error) {
decoder := json.NewDecoder(bytes.NewReader(entry.RawMessage))
decoder.DisallowUnknownFields()
var source Source

switch entry.Source {
case SourceEnv:
var env Env
if err := decoder.Decode(&env); err != nil {
return nil, fmt.Errorf("unmarshalling env source: %w", err)
}
source = &env
case SourceKeyring:
var keyring Keyring
if err := decoder.Decode(&keyring); err != nil {
return nil, fmt.Errorf("unmarshalling keyring source: %w", err)
}
source = &keyring
default:
return nil, fmt.Errorf("unknown source %q", entry.Source)
}

source.Canonicalize()
return source, nil
}

type Config []ConfigEntry

// ConfigEntry is a single entry in the lookup chain.
// This form is used when unmarshalling the config.
type ConfigEntry struct {
// Source is the name of the source used to look up the secret.
Source string `json:"source"`
json.RawMessage
}

type Source interface {
Lookup(binding string) (string, error)
Canonicalize()
}

type Env struct {
// Source is the name of the source used to look up the secret.
// It must be "env".
Source string `json:"source"`
// Name is the name of the environment variable to look up.
Name string `json:"name"`
// Binding binds the value of the environment variable to a well-known name in the helper.
// If not specified, the value is bound to the default secret of the helper.
Binding string `json:"binding,omitempty"`
}

func (e *Env) Lookup(binding string) (string, error) {
if e.Binding != binding {
return "", notFoundError
}
val, ok := os.LookupEnv(e.Name)
if !ok {
return "", notFoundError
}
return val, nil
}

func (e *Env) Canonicalize() {
e.Source = "env"
if e.Binding == "" {
e.Binding = "default"
}
}

type Keyring struct {
// Source is the name of the source used to look up the secret.
// It must be "keyring".
Source string `json:"source"`
// Key is the name of the key to look up in the keyring.
Key string `json:"key"`
// Binding binds the value of the keyring secret to a well-known name in the helper.
// If not specified, the value is bound to the default secret of the helper.
Binding string `json:"binding,omitempty"`
}

func (k *Keyring) Lookup(binding string) (string, error) {
if k.Binding != binding {
return "", notFoundError
}
val, err := keyring.Get("gh:github.com", "")
if errors.Is(err, keyring.ErrNotFound) {
return "", notFoundError
}
if err != nil {
return "", err
}
return val, nil
}

func (k *Keyring) Canonicalize() {
k.Source = "keyring"
if k.Binding == "" {
k.Binding = "default"
}
}

// Default constructs a partially marshalled Config from a slice of specific config entries.
func Default(in []Source) Config {
out := make(Config, len(in))
for i, entry := range in {
// TODO: fix
canonicalizeMethod := reflect.ValueOf(entry).MethodByName("Canonicalize")

if !canonicalizeMethod.IsValid() {
panic(fmt.Sprintf("constructing default config: invalid value at index %d is missing Canonicalize method", i))
}
canonicalizeMethod.Call(nil)

sourceField := reflect.ValueOf(entry).Elem().FieldByName("Source")
if !sourceField.IsValid() || sourceField.Type().Kind() != reflect.String {
panic(fmt.Sprintf("constructing default config: invalid value at index %d is missing Source field", i))
}

raw, err := json.Marshal(entry)
if err != nil {
panic(fmt.Sprintf("constructing default config: invalid value at index %d when marshaling inner config: %v", i, err))
}
out[i] = ConfigEntry{
Source: sourceField.String(),
RawMessage: raw,
}
}
return out
}

var notFoundError = errors.New("not found")
1 change: 1 addition & 0 deletions bzl/private/source_files/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ release_files = [
"//authenticate/github:all_files",
"//authenticate/internal:all_files",
"//authenticate/internal/helperconfig:all_files",
"//authenticate/internal/lookupchain:all_files",
"//authenticate/null:all_files",
"//authenticate/oci:all_files",
"//authenticate/s3:all_files",
Expand Down

0 comments on commit 7284c82

Please sign in to comment.