Skip to content

Commit 861cc24

Browse files
committed
Documentation + refactoring
1 parent 6d13910 commit 861cc24

10 files changed

+180
-150
lines changed

.pre-commit-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ repos:
4848
- id: go-vet
4949
- id: go-cyclo
5050
args: [-over=15]
51+
- id: go-lint

README.md

+19-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
44
![build](https://github.com/gherynos/vault-backend/workflows/build/badge.svg)
55
![release](https://github.com/gherynos/vault-backend/workflows/release/badge.svg)
6+
[![go-report-card](https://goreportcard.com/badge/github.com/gherynos/vault-backend)](https://goreportcard.com/report/github.com/gherynos/vault-backend)
67

78
A Terraform [HTTP backend](https://www.terraform.io/docs/backends/types/http.html) that stores the state in a [Vault secret](https://www.vaultproject.io/docs/secrets/kv/kv-v2).
89

910
The server supports locking and leverages the versioning capabilities of Vault by creating a new secret version when creating/updating the state.
1011

1112
## Terraform config
1213

13-
The server authenticates to Vault using [AppRole](https://www.vaultproject.io/docs/auth/approle), with `role_id` and `secret_id` passed respectively as the `username` and `password` in the configuration.
14+
The server authenticates to Vault using [AppRole](https://www.vaultproject.io/docs/auth/approle), with `role_id` and `secret_id` passed respectively as the `username` and `password` in the configuration:
1415

1516
```terraform
1617
terraform {
@@ -25,14 +26,29 @@ terraform {
2526
}
2627
```
2728

29+
or directly with a [token](https://www.vaultproject.io/docs/auth/token):
30+
31+
```terraform
32+
terraform {
33+
backend "http" {
34+
address = "http://localhost:8080/state/<STATE_NAME>"
35+
lock_address = "http://localhost:8080/state/<STATE_NAME>"
36+
unlock_address = "http://localhost:8080/state/<STATE_NAME>"
37+
38+
username = "TOKEN"
39+
password = "<TOKEN_VALUE>"
40+
}
41+
}
42+
```
43+
2844
where `<STATE_NAME>` is an arbitrary value used to distinguish the backends.
2945

3046
With the above configuration, Terraform connects to a vault-backend server running locally on port 8080 when loading/storing/locking the state, and the server manages the following secrets in Vault:
3147

3248
- `/secret/vbk/<STATE_NAME>`
3349
- `/secret/vbk/<STATE_NAME>-lock`
3450

35-
The latter created when a lock is acquired and deleted when released.
51+
the latter gets created when a lock is acquired and deleted when released.
3652

3753
## Vault Backend config
3854

@@ -73,4 +89,4 @@ path "secret/metadata/vbk/cloud-services-lock"
7389
7490
## License
7591

76-
vault-backend is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
92+
Vault Backend is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).

server/server.go

+68-67
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import (
1313
"os"
1414
)
1515

16+
// Version defines the version of the server
1617
const Version = "0.3.0"
1718

18-
func checkLockId(store s.Store, state, id string) (proceed bool, data string, err error) {
19+
func checkLockID(store s.Store, state, id string) (proceed bool, data string, err error) {
1920

2021
var value []byte
2122
if value, err = store.GetBin(fmt.Sprintf("%s-lock", state)); err != nil {
@@ -75,7 +76,7 @@ func stateHandlerPost(logger *log.Entry, store s.Store, state string, r *http.Re
7576

7677
logger.Debug("Store state")
7778

78-
if proceed, data, err := checkLockId(store, state, r.URL.Query().Get("ID")); err != nil {
79+
if proceed, data, err := checkLockID(store, state, r.URL.Query().Get("ID")); err != nil {
7980

8081
switch err.(type) {
8182

@@ -101,32 +102,32 @@ func stateHandlerPost(logger *log.Entry, store s.Store, state string, r *http.Re
101102
return http.StatusLocked, data
102103
}
103104

104-
if reqBody, err := ioutil.ReadAll(r.Body); err != nil {
105+
var reqBody []byte
106+
var err error
107+
if reqBody, err = ioutil.ReadAll(r.Body); err != nil {
105108

106109
return http.StatusBadRequest, http.StatusText(http.StatusBadRequest)
110+
}
107111

108-
} else {
109-
110-
if err := store.SetBin(state, reqBody); err != nil {
112+
if err := store.SetBin(state, reqBody); err != nil {
111113

112-
switch err.(type) {
114+
switch err.(type) {
113115

114-
case *api.ResponseError:
115-
{
116-
re := err.(*api.ResponseError)
117-
return re.StatusCode, re.Error()
118-
}
116+
case *api.ResponseError:
117+
{
118+
re := err.(*api.ResponseError)
119+
return re.StatusCode, re.Error()
120+
}
119121

120-
default:
121-
{
122-
logger.WithError(err).Error("unable to store state")
123-
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
124-
}
122+
default:
123+
{
124+
logger.WithError(err).Error("unable to store state")
125+
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
125126
}
126127
}
127-
128-
return 200, ""
129128
}
129+
130+
return 200, ""
130131
}
131132

132133
func stateHandlerLock(logger *log.Entry, store s.Store, state string, r *http.Request, w http.ResponseWriter) (int, string) {
@@ -141,18 +142,17 @@ func stateHandlerLock(logger *log.Entry, store s.Store, state string, r *http.Re
141142

142143
case *s.ItemNotFoundError:
143144
{
144-
145-
if reqBody, err := ioutil.ReadAll(r.Body); err != nil {
145+
var reqBody []byte
146+
var err error
147+
if reqBody, err = ioutil.ReadAll(r.Body); err != nil {
146148

147149
return http.StatusBadRequest, http.StatusText(http.StatusBadRequest)
150+
}
148151

149-
} else {
150-
151-
if err := store.SetBin(name, reqBody); err != nil {
152+
if err := store.SetBin(name, reqBody); err != nil {
152153

153-
logger.WithError(err).Error("unable to store lock")
154-
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
155-
}
154+
logger.WithError(err).Error("unable to store lock")
155+
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
156156
}
157157

158158
return 200, ""
@@ -180,66 +180,66 @@ func stateHandlerUnlock(logger *log.Entry, store s.Store, state string, pool s.P
180180

181181
logger.Debug("Unlock state")
182182

183-
if reqBody, err := ioutil.ReadAll(r.Body); err != nil {
183+
var reqBody []byte
184+
var err error
185+
if reqBody, err = ioutil.ReadAll(r.Body); err != nil {
184186

185187
return http.StatusBadRequest, http.StatusText(http.StatusBadRequest)
188+
}
186189

187-
} else {
188-
189-
var body map[string]interface{}
190-
if err := json.Unmarshal(reqBody, &body); err != nil {
190+
var body map[string]interface{}
191+
if err := json.Unmarshal(reqBody, &body); err != nil {
191192

192-
return http.StatusBadRequest, http.StatusText(http.StatusBadRequest)
193-
}
193+
return http.StatusBadRequest, http.StatusText(http.StatusBadRequest)
194+
}
194195

195-
if proceed, data, err := checkLockId(store, state, body["ID"].(string)); err != nil {
196+
if proceed, data, err := checkLockID(store, state, body["ID"].(string)); err != nil {
196197

197-
switch err.(type) {
198+
switch err.(type) {
198199

199-
case *s.ItemNotFoundError:
200-
return http.StatusUnprocessableEntity, http.StatusText(http.StatusUnprocessableEntity)
200+
case *s.ItemNotFoundError:
201+
return http.StatusUnprocessableEntity, http.StatusText(http.StatusUnprocessableEntity)
201202

202-
case *api.ResponseError:
203-
{
204-
re := err.(*api.ResponseError)
205-
return re.StatusCode, re.Error()
206-
}
203+
case *api.ResponseError:
204+
{
205+
re := err.(*api.ResponseError)
206+
return re.StatusCode, re.Error()
207+
}
207208

208-
default:
209-
{
210-
logger.WithError(err).Error("unable to check lock")
211-
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
212-
}
209+
default:
210+
{
211+
logger.WithError(err).Error("unable to check lock")
212+
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
213213
}
214+
}
214215

215-
} else if !proceed {
216+
} else if !proceed {
216217

217-
w.Header().Set("Content-Type", "application/json")
218-
return http.StatusConflict, data
219-
}
218+
w.Header().Set("Content-Type", "application/json")
219+
return http.StatusConflict, data
220+
}
220221

221-
if err := store.Delete(fmt.Sprintf("%s-lock", state)); err != nil {
222+
if err := store.Delete(fmt.Sprintf("%s-lock", state)); err != nil {
222223

223-
switch err.(type) {
224+
switch err.(type) {
224225

225-
case *api.ResponseError:
226-
{
227-
re := err.(*api.ResponseError)
228-
return re.StatusCode, re.Error()
229-
}
226+
case *api.ResponseError:
227+
{
228+
re := err.(*api.ResponseError)
229+
return re.StatusCode, re.Error()
230+
}
230231

231-
default:
232-
{
233-
logger.WithError(err).Error("unable to remove lock")
234-
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
235-
}
232+
default:
233+
{
234+
logger.WithError(err).Error("unable to remove lock")
235+
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
236236
}
237237
}
238+
}
238239

239-
pool.Delete(userPassEnc)
240+
pool.Delete(userPassEnc)
240241

241-
return 200, ""
242-
}
242+
return 200, ""
243243
}
244244

245245
func stateHandler(pool s.Pool, w http.ResponseWriter, r *http.Request) (int, string) {
@@ -330,6 +330,7 @@ func getEnv(key, fallback string) string {
330330
return fallback
331331
}
332332

333+
// RunServer starts the Vault Backend TCP server
333334
func RunServer() {
334335

335336
if _, debug := os.LookupEnv("DEBUG"); debug {

server/server_test.go

+4-6
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,11 @@ func (p *MockPool) Get(identifier string) (val s.Store, err error) {
277277
if val, ok = p.stores[identifier]; ok {
278278

279279
return
280-
281-
} else {
282-
283-
val = NewMockStore()
284-
p.stores[identifier] = val
285-
return
286280
}
281+
282+
val = NewMockStore()
283+
p.stores[identifier] = val
284+
return
287285
}
288286

289287
func (p *MockPool) Delete(identifier string) {

server/vault_pool.go

+25-20
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import (
99
"sync"
1010
)
1111

12+
// VaultPool is an implementation of Pool that manages Vault stores.
1213
type VaultPool struct {
1314
vaultURL, prefix string
1415

1516
stores map[string]*vault.Vault
1617
mutex sync.Mutex
1718
}
1819

20+
// NewVaultPool creates a new pool of Vault stores.
21+
// VaultURL is the URL of the Vault server to connect to.
22+
// prefix is the string prefix used when storing the secrets in Vault.
1923
func NewVaultPool(vaultURL, prefix string) s.Pool {
2024

2125
vp := &VaultPool{vaultURL: vaultURL, prefix: prefix}
@@ -24,6 +28,7 @@ func NewVaultPool(vaultURL, prefix string) s.Pool {
2428
return vp
2529
}
2630

31+
// Get creates or retrieves a Vault store given an identifier.
2732
func (vp *VaultPool) Get(identifier string) (val s.Store, err error) {
2833

2934
vp.mutex.Lock()
@@ -33,38 +38,38 @@ func (vp *VaultPool) Get(identifier string) (val s.Store, err error) {
3338
if val, ok = vp.stores[identifier]; ok {
3439

3540
return
41+
}
3642

37-
} else {
38-
39-
log.Debug("Creating a new Vault client...")
40-
41-
var dec []byte
42-
if dec, err = base64.StdEncoding.DecodeString(identifier); err != nil {
43+
log.Debug("Creating a new Vault client...")
4344

44-
return
45-
}
45+
var dec []byte
46+
if dec, err = base64.StdEncoding.DecodeString(identifier); err != nil {
4647

47-
userPass := strings.Split(string(dec), ":")
48-
var vt *vault.Vault
49-
if userPass[0] == "TOKEN" {
48+
return
49+
}
5050

51-
vt, err = vault.NewWithToken(vp.vaultURL, userPass[1], vp.prefix)
51+
userPass := strings.Split(string(dec), ":")
52+
var vt *vault.Vault
53+
if userPass[0] == "TOKEN" {
5254

53-
} else {
55+
vt, err = vault.NewWithToken(vp.vaultURL, userPass[1], vp.prefix)
5456

55-
vt, err = vault.NewWithAppRole(vp.vaultURL, userPass[0], userPass[1], vp.prefix)
56-
}
57-
if err != nil {
57+
} else {
5858

59-
return
60-
}
59+
vt, err = vault.NewWithAppRole(vp.vaultURL, userPass[0], userPass[1], vp.prefix)
60+
}
61+
if err != nil {
6162

62-
val = vt
63-
vp.stores[identifier] = vt
6463
return
6564
}
65+
66+
val = vt
67+
vp.stores[identifier] = vt
68+
return
6669
}
6770

71+
// Delete removes the Vault store associated with the identifier.
72+
// Invoking delete using a non-existing identifier has no effect.
6873
func (vp *VaultPool) Delete(identifier string) {
6974

7075
vp.mutex.Lock()

store/item_not_found.go

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package store
22

3+
// ItemNotFoundError is an error returned when a requested item is not present in a Store.
34
type ItemNotFoundError struct{}
45

56
func (e *ItemNotFoundError) Error() string {

store/pool.go

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package store
22

3+
// Pool is a collection of Stores.
4+
// The Get method creates a new Store for the given identifier if not already present.
5+
// Trying to delete a Store via an unknown identifier has no effect.
36
type Pool interface {
47
Get(identifier string) (Store, error)
58

store/store.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package store
22

3+
// Store is a collection of byte arrays.
4+
// The byte arrays can be stored, retrieved and deleted by name.
35
type Store interface {
46
SetBin(name string, data []byte) error
57

0 commit comments

Comments
 (0)