Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add bitwarden secrets manager backend #657

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
47 changes: 46 additions & 1 deletion docs/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -807,4 +807,49 @@ metadata:
type: Opaque
data:
password: <path:prod:my-secret#key>
```
```

### Bitwarden Secrets Manager

**Note**: The Bitwarden Secrets Manager backend does not support versioning.

##### Authentication

These are the parameters for Delinea:
```
AVP_TYPE: bitwardensecretsmanager
AVP_BITWARDEN_TOKEN: Bitwarden machine Account Token

Optional:
AVP_BITWARDEN_API_URL: API Endpoint URL (default: https://api.bitwarden.com)
AVP_BITWARDEN_IDENTITY_URL: Identity Endpoint URL (default: https://identity.bitwarden.com)
```
##### Examples
Examples assume that the secrets are not saved base64 encoded in the Secret Server.

###### Path Annotation

```yaml
kind: Secret
apiVersion: v1
metadata:
name: test-secret
annotations:
avp.kubernetes.io/path: "organization-id"
type: Opaque
stringData:
password: <secret-id>
```

###### Inline Path

```yaml
kind: Secret
apiVersion: v1
metadata:
name: test-secret
type: Opaque
data:
password: <path:organization-id#secret-id | base64encode>
```

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