Skip to content

Commit

Permalink
Merge pull request #158 from maxlaverse/embedded_client
Browse files Browse the repository at this point in the history
implement a client to interact with Bitwarden servers directly
  • Loading branch information
maxlaverse authored Sep 29, 2024
2 parents 75a1ddf + 5b1e4b1 commit fae5d00
Show file tree
Hide file tree
Showing 37 changed files with 2,893 additions and 395 deletions.
18 changes: 15 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
env:
ADMIN_TOKEN: test1234
I_REALLY_WANT_VOLATILE_STORAGE: "true"
DISABLE_ICON_DOWNLOAD: "false"
DISABLE_ICON_DOWNLOAD: "true"
LOGIN_RATELIMIT_SECONDS: "1"
LOGIN_RATELIMIT_MAX_BURST: "1000000"
ADMIN_RATELIMIT_SECONDS: "1"
Expand Down Expand Up @@ -96,13 +96,25 @@ jobs:
- name: Build
run: go build -v ./...

- name: Test
run: go test -failfast -coverprofile=profile.cov -v ./...
- name: Test with Embedded Client
run: go test -coverprofile=profile_embedded.cov -v ./...
env:
VAULTWARDEN_HOST: "127.0.0.1"
VAULTWARDEN_PORT: "8080"
TF_ACC: "1"
TEST_USE_EMBEDDED_CLIENT: "1"

- name: Test with Official Client
run: go test -coverprofile=profile_official.cov -timeout 1000s -failfast -v ./...
env:
VAULTWARDEN_HOST: "127.0.0.1"
VAULTWARDEN_PORT: "8080"
TF_ACC: "1"

- name: Combine Coverage
run: cat profile_embedded.cov profile_official.cov | sort -r | uniq > profile.cov
- name: Code Coverage
continue-on-error: true
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: profile.cov
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ This project is not associated with the Bitwarden project nor 8bit Solutions LLC

## Supported Versions
The plugin has been tested and built with the following components:
- [Terraform] v1.6.1
- [Bitwarden CLI] v2023.2.0
- [Go] 1.22.0 (for development)
- [Terraform] v1.6.1 / [OpenTofu] v1.8.0
- [Bitwarden CLI] v2023.2.0 (except with the experimental `embedded_client`)
- [Go] 1.23.1 (for development)
- [Docker] 23.0.5 (for development)

The provider likely works with older versions but those haven't been tested.
Expand All @@ -45,6 +45,13 @@ terraform {
# Configure the Bitwarden Provider
provider "bitwarden" {
email = "[email protected]"
# If you have the opportunity, you can try out the embedded client which removes the need for a locally installed Bitwarden CLI.
# Please note that this feature is still considered as experimental, might not work as expected, and is not recommended for production use.
#
# experimental {
# embedded_client = true
# }
}
# Create a Bitwarden Login item
Expand Down Expand Up @@ -84,7 +91,7 @@ In order to run the full suite of Acceptance tests, start a Vaultwarden server:
```sh
$ docker run -ti \
-e I_REALLY_WANT_VOLATILE_STORAGE=true \
-e DISABLE_ICON_DOWNLOAD=false \
-e DISABLE_ICON_DOWNLOAD=true \
-e ADMIN_TOKEN=test1234 \
-e LOGIN_RATELIMIT_SECONDS=1 \
-e LOGIN_RATELIMIT_MAX_BURST=1000000 \
Expand All @@ -106,9 +113,10 @@ $ make testacc
Distributed under the Mozilla License. See [LICENSE](./LICENSE) for more information.

[Terraform]: https://www.terraform.io/downloads.html
[OpenTofu]: https://opentofu.org/
[Go]: https://golang.org/doc/install
[Bitwarden CLI]: https://bitwarden.com/help/article/cli/#download-and-install
[Docker]: https://www.docker.com/products/docker-desktop
[Terraform Registry docs]: https://registry.terraform.io/providers/maxlaverse/bitwarden/latest/docs
[hashicorp/terraform-plugin-sdk#63]: https://github.com/hashicorp/terraform-plugin-sdk/issues/63
[Terraform's documentation on Data Storage]: https://bitwarden.com/help/data-storage/#on-your-local-machine
[Terraform's documentation on Data Storage]: https://bitwarden.com/help/data-storage/#on-your-local-machine
15 changes: 15 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ provider "bitwarden" {
master_password = "my-master-password"
client_id = "my-client-id"
client_secret = "my-client-secret"
# If you have the opportunity, you can try out the embedded client which removes the need for a locally installed Bitwarden CLI.
# Please note that this feature is still considered as experimental, might not work as expected, and is not recommended for production use.
#
# experimental {
# embedded_client = true
# }
}
```

Expand Down Expand Up @@ -115,12 +122,20 @@ export BW_CLIENTSECRET="my-client-secret"

- `client_id` (String) Client ID (env: `BW_CLIENTID`)
- `client_secret` (String) Client Secret (env: `BW_CLIENTSECRET`). Do not commit this information in Git unless you know what you're doing. Prefer using a Terraform `variable {}` in order to inject this value from the environment.
- `experimental` (Block Set) Enable experimental features. (see [below for nested schema](#nestedblock--experimental))
- `extra_ca_certs` (String) Extends the well known 'root' CAs (like VeriSign) with the extra certificates in file (env: `NODE_EXTRA_CA_CERTS`).
- `master_password` (String) Master password of the Vault (env: `BW_PASSWORD`). Do not commit this information in Git unless you know what you're doing. Prefer using a Terraform `variable {}` in order to inject this value from the environment.
- `server` (String) Bitwarden Server URL (default: `https://vault.bitwarden.com`, env: `BW_URL`).
- `session_key` (String) A Bitwarden Session Key (env: `BW_SESSION`)
- `vault_path` (String) Alternative directory for storing the Vault locally (default: `.bitwarden/`, env: `BITWARDENCLI_APPDATA_DIR`).

<a id="nestedblock--experimental"></a>
### Nested Schema for `experimental`

Optional:

- `embedded_client` (Boolean) Use the embedded client instead of an external binary.

[Bitwarden]: https://bitwarden.com/help/article/managing-items/
[Bitwarden CLI]: https://bitwarden.com/help/article/cli/#download-and-install

Expand Down
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
module github.com/maxlaverse/terraform-provider-bitwarden

go 1.22
go 1.23

require (
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/hashicorp/terraform-plugin-docs v0.19.4
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0
github.com/jarcoal/httpmock v1.3.1
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.27.0
)
Expand All @@ -28,12 +31,11 @@ require (
github.com/fatih/color v1.16.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/cli v1.1.6 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
Expand Down
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
Expand Down Expand Up @@ -116,6 +118,8 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
Expand All @@ -140,6 +144,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
Expand Down
9 changes: 8 additions & 1 deletion internal/bitwarden/bwcli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (c *client) CreateAttachment(ctx context.Context, itemId string, filePath s
}

func (c *client) EditObject(ctx context.Context, obj models.Object) (*models.Object, error) {
obj.Groups = []interface{}{}
objEncoded, err := c.encode(obj)
if err != nil {
return nil, err
Expand All @@ -141,9 +142,13 @@ func (c *client) EditObject(ctx context.Context, obj models.Object) (*models.Obj
"edit",
string(obj.Object),
obj.ID,
objEncoded,
}

if obj.Object == models.ObjectTypeOrgCollection {
args = append(args, "--organizationid", obj.OrganizationID)
}
args = append(args, []string{objEncoded}...)

out, err := c.cmdWithSession(args...).Run(ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -247,6 +252,7 @@ func (c *client) Logout(ctx context.Context) error {
}

func (c *client) DeleteObject(ctx context.Context, obj models.Object) error {
// TODO: Don't fail if object is already gone
args := []string{
"delete",
string(obj.Object),
Expand All @@ -262,6 +268,7 @@ func (c *client) DeleteObject(ctx context.Context, obj models.Object) error {
}

func (c *client) DeleteAttachment(ctx context.Context, itemId, attachmentId string) error {
// TODO: Don't fail if attachment is already gone
_, err := c.cmdWithSession("delete", string(models.ObjectTypeAttachment), attachmentId, "--itemid", itemId).Run(ctx)
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/bitwarden/bwcli/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

func TestCreateObjectEncoding(t *testing.T) {
removeMocks, commandsExecuted := test_command.MockCommands(t, map[string]string{
"create item eyJncm91cHMiOltdLCJsb2dpbiI6e30sIm9iamVjdCI6Iml0ZW0iLCJzZWN1cmVOb3RlIjp7fSwidHlwZSI6MSwiZmllbGRzIjpbeyJuYW1lIjoidGVzdCIsInZhbHVlIjoicGFzc2VkIiwidHlwZSI6MCwibGlua2VkSWQiOm51bGx9XX0": `{}`,
"create item eyJmaWVsZHMiOlt7Im5hbWUiOiJ0ZXN0IiwidmFsdWUiOiJwYXNzZWQiLCJ0eXBlIjowLCJsaW5rZWRJZCI6bnVsbH1dLCJncm91cHMiOltdLCJsb2dpbiI6e30sIm9iamVjdCI6Iml0ZW0iLCJzZWN1cmVOb3RlIjp7fSwidHlwZSI6MX0": `{}`,
})
defer removeMocks(t)

Expand All @@ -31,7 +31,7 @@ func TestCreateObjectEncoding(t *testing.T) {

assert.NoError(t, err)
if assert.Len(t, commandsExecuted(), 1) {
assert.Equal(t, "create item eyJncm91cHMiOltdLCJsb2dpbiI6e30sIm9iamVjdCI6Iml0ZW0iLCJzZWN1cmVOb3RlIjp7fSwidHlwZSI6MSwiZmllbGRzIjpbeyJuYW1lIjoidGVzdCIsInZhbHVlIjoicGFzc2VkIiwidHlwZSI6MCwibGlua2VkSWQiOm51bGx9XX0", commandsExecuted()[0])
assert.Equal(t, "create item eyJmaWVsZHMiOlt7Im5hbWUiOiJ0ZXN0IiwidmFsdWUiOiJwYXNzZWQiLCJ0eXBlIjowLCJsaW5rZWRJZCI6bnVsbH1dLCJncm91cHMiOltdLCJsb2dpbiI6e30sIm9iamVjdCI6Iml0ZW0iLCJzZWN1cmVOb3RlIjp7fSwidHlwZSI6MX0", commandsExecuted()[0])
}
}

Expand Down
64 changes: 64 additions & 0 deletions internal/bitwarden/crypto/encryptedstring/encryptedstring.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package encryptedstring

import (
"bytes"
"encoding/base64"
"fmt"
"strconv"
Expand All @@ -9,6 +10,13 @@ import (
"github.com/maxlaverse/terraform-provider-bitwarden/internal/bitwarden/crypto/symmetrickey"
)

const (
BUFFER_ENC_TYPE_LENGTH = 1
BUFFER_IV_LENGTH = 16
BUFFER_MAC_LENGTH = 32
BUFFER_MIN_DATA_LENGTH = 1
)

type EncryptedString struct {
IV []byte
Data []byte
Expand All @@ -25,6 +33,62 @@ func New(iv, data, hmac []byte, key symmetrickey.Key) EncryptedString {
}
}

func NewFromEncryptedBuffer(encBytes []byte) (*EncryptedString, error) {
encType := symmetrickey.EncryptionType(encBytes[0])

encString := EncryptedString{}

switch encType {
case symmetrickey.AesCbc256_HmacSha256_B64:
const minimumLength = BUFFER_ENC_TYPE_LENGTH + BUFFER_IV_LENGTH + BUFFER_MAC_LENGTH + BUFFER_MIN_DATA_LENGTH
if len(encBytes) < minimumLength {
return nil, fmt.Errorf("bad minimum length for encrypted buffer: %d", len(encBytes))
}
encString.Key.EncryptionType = encType
encString.IV = encBytes[BUFFER_ENC_TYPE_LENGTH : BUFFER_ENC_TYPE_LENGTH+BUFFER_IV_LENGTH]
encString.Hmac = encBytes[BUFFER_ENC_TYPE_LENGTH+BUFFER_IV_LENGTH : BUFFER_ENC_TYPE_LENGTH+BUFFER_IV_LENGTH+BUFFER_MAC_LENGTH]
encString.Data = encBytes[BUFFER_ENC_TYPE_LENGTH+BUFFER_IV_LENGTH+BUFFER_MAC_LENGTH:]
default:
return nil, fmt.Errorf("unsupported encrypted buffer type: %d", encType)
}

return &encString, nil
}

func (encString *EncryptedString) ToEncryptedBuffer() ([]byte, error) {
if len(encString.IV) != BUFFER_IV_LENGTH {
return nil, fmt.Errorf("can't output encrypted buffer: bad IV length")
}
if len(encString.Hmac) != BUFFER_MAC_LENGTH {
return nil, fmt.Errorf("can't output encrypted buffer: bad HMAC length")
}
if len(encString.Data) < BUFFER_MIN_DATA_LENGTH {
return nil, fmt.Errorf("can't output encrypted buffer: bad data length")
}
encType := []byte{byte(encString.Key.EncryptionType)}
encBuffer := append(encType, encString.IV...)
encBuffer = append(encBuffer, encString.Hmac...)
encBuffer = append(encBuffer, encString.Data...)

return encBuffer, nil
}

func (encString *EncryptedString) Equals(otherString *EncryptedString) bool {
if !bytes.Equal(encString.Data, otherString.Data) {
return false
}
if !bytes.Equal(encString.IV, otherString.IV) {
return false
}
if !bytes.Equal(encString.Hmac, otherString.Hmac) {
return false
}
if encString.Key.EncryptionType != otherString.Key.EncryptionType {
return false
}
return true
}

func NewFromEncryptedValue(encryptedValue string) (*EncryptedString, error) {
var encPieces []string
encString := EncryptedString{}
Expand Down
Loading

0 comments on commit fae5d00

Please sign in to comment.