Skip to content

Commit

Permalink
feat: add bitwarden secrets manager backend
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Cox <[email protected]>
  • Loading branch information
Khabi committed Aug 9, 2024
1 parent 810f1fe commit fd2ade0
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 56 deletions.
3 changes: 2 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ builds:
-X "github.com/argoproj-labs/argocd-vault-plugin/version.BuildDate={{.Date}}"
-X "github.com/argoproj-labs/argocd-vault-plugin/version.CommitSHA={{.Commit}}"
env:
- "CGO_ENABLED=0"
- "CGO_ENABLED=1"
- "C=musl-gcc"
goos:
- darwin
- linux
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ADD go.mod go.mod
ADD go.sum go.sum

ENV GOPATH=""
RUN go mod download
RUN apt-get update && apt install musl-tools -y && go mod download

VOLUME work
WORKDIR work
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
BINARY=argocd-vault-plugin
LD_FLAGS=-ldflags '-linkmode external -extldflags "-static -Wl,-unresolved-symbols=ignore-all"'

default: build

quality:
go vet github.com/argoproj-labs/argocd-vault-plugin
go test -race -v -coverprofile cover.out ./...
go test ${LD_FLAGS} -race -v -coverprofile cover.out ./...

build:
go build -buildvcs=false -o ${BINARY} .
C=musl-gcc CGO_ENABLED=1 go build ${LD_FLAGS} -buildvcs=false -o ${BINARY} .

test:
C=musl-gcc CGO_ENABLED=1 go test ${LD_FLAGS} -buildvcs=false ./...

install: build

e2e: install
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.27.1
github.com/aws/aws-sdk-go-v2/config v1.27.17
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.29.2
github.com/bitwarden/sdk-go v0.1.1
github.com/googleapis/gax-go/v2 v2.12.0
github.com/hashicorp/go-hclog v1.6.2
github.com/hashicorp/vault v1.16.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitwarden/sdk-go v0.1.1 h1:Fn7d0SuThIEwaIecg3SRBM6RUbUyQQ7x7Ex+qrcLbMA=
github.com/bitwarden/sdk-go v0.1.1/go.mod h1:Gp2ADXAL0XQ3GO3zxAv503xSlL6ORPf0VZg2J+yQ6jU=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
Expand Down
64 changes: 64 additions & 0 deletions pkg/backends/bitwardensecretsmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package backends

import (
"fmt"

bitwarden "github.com/bitwarden/sdk-go"
)

type BitwardenSecretsClient interface {
List(string) (*bitwarden.SecretIdentifiersResponse, error)
Get(string) (*bitwarden.SecretResponse, error)
}

// BitwardenSecretsManager is a struct for working with a Bitwarden Secrets Manager backend
type BitwardenSecretsManager struct {
Client BitwardenSecretsClient
}

// NewBitwardenSecretsClient initializes a new Bitwarden Secrets Manager backend
func NewBitwardenSecretsClient(client BitwardenSecretsClient) *BitwardenSecretsManager {
return &BitwardenSecretsManager{
Client: client,
}
}

// Login does nothing as a "login" is handled on the instantiation of the Bitwarden SDK
func (bw *BitwardenSecretsManager) Login() error {
return nil
}

// GetSecrets gets secrets from Bitwarden Secrets Manager and returns the formatted data
// The path is of format `organization-id
func (bw *BitwardenSecretsManager) GetSecrets(path string, _ string, _ map[string]string) (map[string]interface{}, error) {
result := make(map[string]interface{})

secrets, err := bw.Client.List(path)
if err != nil {
return nil, err
}

for _, secret := range secrets.Data {
value, err := bw.Client.Get(secret.ID)
if err != nil {
return nil, err
}
result[secret.ID] = value.Value
}

return result, nil
}

// GetIndividualSecret will get the specific secret (placeholder) from the SM backend
// The path is of format `organization-id/secret-id`
// organization id is ignored for indvidual secret fetching, but is included here to
// keep a standard path.
// Version is not supported and is ignored.
func (bw *BitwardenSecretsManager) GetIndividualSecret(_, secret, _ string, _ map[string]string) (interface{}, error) {
fmt.Println(secret)
value, err := bw.Client.Get(secret)
if err != nil {
return nil, err
}
return value.Value, nil
}
98 changes: 98 additions & 0 deletions pkg/backends/bitwardensecretsmanager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package backends_test

import (
"testing"

"github.com/argoproj-labs/argocd-vault-plugin/pkg/backends"
bitwarden "github.com/bitwarden/sdk-go"
)

type mockBitwardenSecretesClient struct{}

func (bw mockBitwardenSecretesClient) List(organizationID string) (*bitwarden.SecretIdentifiersResponse, error) {
switch organizationID {
case "58293c58-5666-11ef-91a2-67fcd9d549c7":
return &bitwarden.SecretIdentifiersResponse{
Data: []bitwarden.SecretIdentifierResponse{
{
ID: "ce398fa2-5665-11ef-8916-97605d6da25b",
Key: "Human Readable Key",
OrganizationID: organizationID,
},
{
ID: "98b6c8ee-5666-11ef-ac37-8742ac5fc78f",
Key: "Other Key",
OrganizationID: organizationID,
},
},
}, nil
default:
return nil, nil
}
}

func (bw mockBitwardenSecretesClient) Get(secretID string) (*bitwarden.SecretResponse, error) {
switch secretID {
case "ce398fa2-5665-11ef-8916-97605d6da25b":
projectID := "ddb13dae-5665-11ef-8583-f73233caa8df"
return &bitwarden.SecretResponse{
CreationDate: "2022-11-17T15:55:18.005669100Z",
ID: secretID,
Key: "Human Readable Key",
Note: "",
OrganizationID: "d4105690-5665-11ef-a058-c713a9374bb0",
ProjectID: &projectID,
RevisionDate: "2022-11-17T15:55:18.005669100Z",
Value: "my secret",
}, nil
case "98b6c8ee-5666-11ef-ac37-8742ac5fc78f":
projectID := "ddb13dae-5665-11ef-8583-f73233caa8df"
return &bitwarden.SecretResponse{
CreationDate: "2019-05-11T15:55:18.005669100Z",
ID: secretID,
Key: "Other Key",
Note: "",
OrganizationID: "d4105690-5665-11ef-a058-c713a9374bb0",
ProjectID: &projectID,
RevisionDate: "2019-05-11T15:55:18.005669100Z",
Value: "my other secret",
}, nil
default:
return nil, nil
}

}

func TestBitwardenSecretsManager(t *testing.T) {
sm := backends.NewBitwardenSecretsClient(mockBitwardenSecretesClient{})

t.Run("Test Login", func(t *testing.T) {
err := sm.Login()
if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
})

t.Run("GetIndividualSecret", func(t *testing.T) {
secret, err := sm.GetIndividualSecret("58293c58-5666-11ef-91a2-67fcd9d549c7", "ce398fa2-5665-11ef-8916-97605d6da25b", "", map[string]string{})
if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
if secret != "my secret" {
t.Fatalf("expected secret value 'my secret' but got: %s", secret)
}
})

t.Run("GetSecrets", func(t *testing.T) {
secrets, err := sm.GetSecrets("58293c58-5666-11ef-91a2-67fcd9d549c7", "", map[string]string{})
if err != nil {
t.Fatalf("expected 0 errors but got: %s", err)
}
if secrets["ce398fa2-5665-11ef-8916-97605d6da25b"] != "my secret" {
t.Fatalf("expected 'my secret' but got: %s", secrets["ce398fa2-5665-11ef-8916-97605d6da25b"])
}
if secrets["98b6c8ee-5666-11ef-ac37-8742ac5fc78f"] != "my other secret" {
t.Fatalf("expected 'my other secret' but got: %s", secrets["98b6c8ee-5666-11ef-ac37-8742ac5fc78f"])
}
})
}
40 changes: 39 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"bytes"
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
"os"
"strconv"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"

gcpsm "cloud.google.com/go/secretmanager/apiv1"
"github.com/1Password/connect-sdk-go/connect"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
Expand All @@ -23,6 +24,7 @@ import (
"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
"github.com/aws/aws-sdk-go-v2/config"
awssm "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
bitwarden "github.com/bitwarden/sdk-go"
"github.com/hashicorp/vault/api"
ksm "github.com/keeper-security/secrets-manager-go/core"
"github.com/spf13/viper"
Expand Down Expand Up @@ -50,6 +52,7 @@ var backendPrefixes []string = []string{
"sops",
"op_connect",
"k8s_secret",
"bitwarden",
}

// New returns a new Config struct
Expand Down Expand Up @@ -283,6 +286,41 @@ func New(v *viper.Viper, co *Options) (*Config, error) {
{
backend = backends.NewKubernetesSecret()
}
case types.BitwardenSecretsManagerBackend:
{
if !v.IsSet(types.EnvAvpBitwardenAPIURL) {
utils.VerboseToStdErr("warning: %s env var not set, using Bitwarden API URL %s", types.EnvAvpBitwardenAPIURL, types.BitwardenDefaultAPIURL)
v.Set(types.EnvAvpBitwardenAPIURL, types.BitwardenDefaultAPIURL)
}

if !v.IsSet(types.EnvAvpBitwardenIdentityURL) {
utils.VerboseToStdErr("warning: %s env var not set, using Bitwarden Identity URL %s", types.EnvAvpBitwardenIdentityURL, types.BitwardenDefaultIdentityURL)
v.Set(types.EnvAvpBitwardenIdentityURL, types.BitwardenDefaultIdentityURL)
}

if !v.IsSet(types.EnvAvpBitwardenToken) {
return nil, fmt.Errorf("%s is required for Bitwarden Secrets Manager", types.EnvAvpBitwardenToken)
}

apiURL := v.GetString(types.EnvAvpBitwardenAPIURL)
identityURL := v.GetString(types.EnvAvpBitwardenIdentityURL)

s, err := bitwarden.NewBitwardenClient(
&apiURL,
&identityURL,
)
if err != nil {
return nil, err
}

err = s.AccessTokenLogin(v.GetString(types.EnvAvpBitwardenToken), nil)
if err != nil {
return nil, err
}

backend = backends.NewBitwardenSecretsClient(s.Secrets())

}
default:
return nil, fmt.Errorf("Must provide a supported Vault Type, received %s", v.GetString(types.EnvAvpType))
}
Expand Down
Loading

0 comments on commit fd2ade0

Please sign in to comment.