Skip to content

Commit 3c68be1

Browse files
authored
Merge pull request #36 from Keyfactor/OauthAndParametersForTemplateAndCA
Updated with OAuth Support and the ability to pass CA and Template via cli
2 parents 1c8de0f + 3d870ea commit 3c68be1

18 files changed

+1422
-1282
lines changed

.github/workflows/release.yml

Lines changed: 0 additions & 43 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
- 1.4.0
2+
- Added support for oAuth2 authentication to Keyfactor Command.
3+
- Included the ability to specify CA and Template via command parameters
4+
- Included the ability to pass metadata along with the request
5+
16
- 1.3.1
27
- Fix for issue where plugin was not enforcing plugin-side role limitations for AllowedDomains and AllowSubDomains, and was relying exclusively on the certificate template for these values.
38

Makefile

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,56 @@
11
BINARY = "keyfactor"
22
VERSION = "v1.3.1"
33

4-
GOARCH = amd64
4+
UNAME_S := $(shell uname -s)
5+
UNAME_M := $(shell uname -m)
56

6-
UNAME = $(shell uname -s)
7-
8-
OS = linux
7+
ifeq ($(UNAME_S),Linux)
8+
OS = linux
9+
else ifeq ($(UNAME_S),Darwin)
10+
OS = darwin
11+
endif
912

10-
ifndef OS
11-
ifeq ($(UNAME), Linux)
12-
OS = linux
13-
else ifeq ($(UNAME), Darwin)
14-
OS = darwin
15-
endif
13+
ifeq ($(UNAME_M),x86_64)
14+
GOARCH = amd64
15+
else ifeq ($(UNAME_M),arm64)
16+
GOARCH = arm64
17+
else ifeq ($(UNAME_M),i386)
18+
GOARCH = 386
1619
endif
1720

1821
.DEFAULT_GOAL := all
1922

2023
all: fmt build start
2124

2225
build:
23-
GOOS=$(OS) GOARCH="$(GOARCH)" go build -o vault/plugins/keyfactor cmd/keyfactor/main.go
26+
GOOS=$(OS) GOARCH=$(GOARCH) go build -o vault/plugins/keyfactor cmd/keyfactor/main.go
2427

2528
start:
26-
vault server -dev -dev-root-token-id=root -dev-plugin-dir=/vault/plugins
29+
vault server -dev -dev-root-token-id=root -dev-plugin-dir=./vault/plugins
30+
31+
register:
32+
vault write sys/plugins/catalog/secret/keyfactor sha_256=$(shell shasum -a 256 ./vault/plugins/keyfactor | cut -d ' ' -f 1) command="keyfactor"
2733

2834
enable:
35+
export VAULT_ADDR=http://localhost:8200
36+
export VAULT_TOKEN=root
2937
vault secrets enable keyfactor
3038

39+
config_oauth:
40+
vault write keyfactor/config \
41+
url="https://int1230-oauth.eastus2.cloudapp.azure.com" \
42+
client_id="vault-secrets-engine" \
43+
client_secret="c6rxzs6Hz8JjlkFR87ra18WBqlhXdwMO" \
44+
token_url="https://int1230-oauth.eastus2.cloudapp.azure.com/oauth2/token" \
45+
template="SslServerProfile" \
46+
CA="TestDriveSub-G1"
47+
3148
clean:
3249
rm -f ./vault/plugins/keyfactor
3350

3451
fmt:
3552
go fmt $$(go list ./...)
3653

37-
3854
release:
3955
GOOS=darwin GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_darwin_amd64
4056
GOOS=freebsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_freebsd_386
@@ -49,5 +65,4 @@ release:
4965
GOOS=windows GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_windows_386
5066
GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64
5167

52-
53-
.PHONY: build clean fmt start enable
68+
.PHONY: build clean fmt start enable register release

README.md

Lines changed: 244 additions & 180 deletions
Large diffs are not rendered by default.

backend.go

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@
77
* and limitations under the License.
88
*/
99

10-
package keyfactor
10+
package kfbackend
1111

1212
import (
1313
"context"
14-
b64 "encoding/base64"
1514
"encoding/json"
1615
"errors"
1716
"fmt"
1817
"io"
19-
"io/ioutil"
2018
"net/http"
2119
"strings"
2220
"sync"
@@ -27,7 +25,9 @@ import (
2725
"github.com/hashicorp/vault/sdk/logical"
2826
)
2927

30-
//var config map[string]string
28+
const (
29+
operationPrefixKeyfactor string = "keyfactor"
30+
)
3131

3232
// Factory configures and returns backend
3333
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
@@ -42,7 +42,7 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
4242
// // Store certificates by serial number
4343
type keyfactorBackend struct {
4444
*framework.Backend
45-
lock sync.RWMutex
45+
configLock sync.RWMutex
4646
cachedConfig *keyfactorConfig
4747
client *keyfactorClient
4848
}
@@ -68,19 +68,31 @@ func backend() *keyfactorBackend {
6868
pathCA(&b),
6969
pathCerts(&b),
7070
),
71-
Secrets: []*framework.Secret{},
72-
BackendType: logical.TypeLogical,
73-
Invalidate: b.invalidate,
71+
Secrets: []*framework.Secret{},
72+
BackendType: logical.TypeLogical,
73+
Invalidate: b.invalidate,
74+
InitializeFunc: b.Initialize,
7475
}
7576
return &b
7677
}
7778

7879
// reset clears any client configuration for a new
7980
// backend to be configured
8081
func (b *keyfactorBackend) reset() {
81-
b.lock.Lock()
82-
defer b.lock.Unlock()
82+
b.configLock.RLock()
83+
defer b.configLock.RUnlock()
84+
b.cachedConfig = nil
8385
b.client = nil
86+
87+
}
88+
89+
func (b *keyfactorBackend) Initialize(ctx context.Context, req *logical.InitializationRequest) error {
90+
b.configLock.RLock()
91+
defer b.configLock.RUnlock()
92+
if req == nil {
93+
return fmt.Errorf("initialization request is nil")
94+
}
95+
return nil
8496
}
8597

8698
// invalidate clears an existing client configuration in
@@ -94,63 +106,73 @@ func (b *keyfactorBackend) invalidate(ctx context.Context, key string) {
94106
// getClient locks the backend as it configures and creates a
95107
// a new client for the target API
96108
func (b *keyfactorBackend) getClient(ctx context.Context, s logical.Storage) (*keyfactorClient, error) {
97-
b.lock.RLock()
98-
unlockFunc := b.lock.RUnlock
99-
defer func() { unlockFunc() }()
109+
b.configLock.RLock()
110+
defer b.configLock.RUnlock()
100111

101112
if b.client != nil {
102113
return b.client, nil
103114
}
104115

105-
b.lock.RUnlock()
106-
b.lock.Lock()
107-
unlockFunc = b.lock.Unlock
116+
// get configuration
117+
config, err := b.fetchConfig(ctx, s)
118+
if err != nil {
119+
return nil, err
120+
}
121+
if config == nil {
122+
return nil, errors.New("configuration is empty")
123+
}
108124

109-
return nil, fmt.Errorf("need to return client")
125+
b.client, err = newClient(config, b)
126+
if err != nil {
127+
return nil, err
128+
}
129+
return b.client, nil
110130
}
111131

112132
// Handle interface with Keyfactor API to enroll a certificate with given content
113-
func (b *keyfactorBackend) submitCSR(ctx context.Context, req *logical.Request, csr string, caName string, templateName string) ([]string, string, error) {
114-
config, err := b.config(ctx, req.Storage)
133+
func (b *keyfactorBackend) submitCSR(ctx context.Context, req *logical.Request, csr string, caName string, templateName string, metaDataJson string) ([]string, string, error) {
134+
config, err := b.fetchConfig(ctx, req.Storage)
115135
if err != nil {
116136
return nil, "", err
117137
}
118138
if config == nil {
119139
return nil, "", errors.New("configuration is empty")
120140
}
121141

122-
ca := config.CertAuthority
123-
template := config.CertTemplate
124-
125-
creds := config.Username + ":" + config.Password
126-
encCreds := b64.StdEncoding.EncodeToString([]byte(creds))
127-
128142
location, _ := time.LoadLocation("UTC")
129143
t := time.Now().In(location)
130144
time := t.Format("2006-01-02T15:04:05")
131145

132-
// This is only needed when running as a vault extension
146+
// get client
147+
client, err := b.getClient(ctx, req.Storage)
148+
if err != nil {
149+
return nil, "", fmt.Errorf("error getting client: %w", err)
150+
}
151+
133152
b.Logger().Debug("Closing idle connections")
134-
http.DefaultClient.CloseIdleConnections()
153+
client.httpClient.CloseIdleConnections()
154+
155+
// build request parameter structure
135156

136-
// Build request
137-
url := config.KeyfactorUrl + "/KeyfactorAPI/Enrollment/CSR"
157+
url := config.KeyfactorUrl + "/" + config.CommandAPIPath + "/Enrollment/CSR"
138158
b.Logger().Debug("url: " + url)
139-
bodyContent := "{\"CSR\": \"" + csr + "\",\"CertificateAuthority\":\"" + ca + "\",\"IncludeChain\": true, \"Metadata\": {}, \"Timestamp\": \"" + time + "\",\"Template\": \"" + template + "\",\"SANs\": {}}"
159+
bodyContent := "{\"CSR\": \"" + csr + "\",\"CertificateAuthority\":\"" + caName + "\",\"IncludeChain\": true, \"Metadata\": " + metaDataJson + ", \"Timestamp\": \"" + time + "\",\"Template\": \"" + templateName + "\",\"SANs\": {}}"
140160
payload := strings.NewReader(bodyContent)
141161
b.Logger().Debug("body: " + bodyContent)
142162
httpReq, err := http.NewRequest("POST", url, payload)
163+
143164
if err != nil {
144165
b.Logger().Info("Error forming request: {{err}}", err)
145166
}
167+
146168
httpReq.Header.Add("x-keyfactor-requested-with", "APIClient")
147169
httpReq.Header.Add("content-type", "application/json")
148-
httpReq.Header.Add("authorization", "Basic "+encCreds)
149170
httpReq.Header.Add("x-certificateformat", "PEM")
150171

151172
// Send request and check status
173+
152174
b.Logger().Debug("About to connect to " + config.KeyfactorUrl + "for csr submission")
153-
res, err := http.DefaultClient.Do(httpReq)
175+
res, err := client.httpClient.Do(httpReq)
154176
if err != nil {
155177
b.Logger().Info("CSR Enrollment failed: {{err}}", err.Error())
156178
return nil, "", err
@@ -166,7 +188,7 @@ func (b *keyfactorBackend) submitCSR(ctx context.Context, req *logical.Request,
166188
// Read response and return certificate and key
167189

168190
defer res.Body.Close()
169-
body, err := ioutil.ReadAll(res.Body)
191+
body, err := io.ReadAll(res.Body)
170192
if err != nil {
171193
b.Logger().Error("Error reading response: {{err}}", err)
172194
return nil, "", err
@@ -233,3 +255,15 @@ func (b *keyfactorBackend) submitCSR(ctx context.Context, req *logical.Request,
233255
const keyfactorHelp = `
234256
The Keyfactor backend is a pki service that issues and manages certificates.
235257
`
258+
259+
func (b *keyfactorBackend) isValidJSON(str string) bool {
260+
var js json.RawMessage
261+
err := json.Unmarshal([]byte(str), &js)
262+
if err != nil {
263+
b.Logger().Debug(err.Error())
264+
return false
265+
} else {
266+
b.Logger().Debug("the metadata was able to be parsed as valid JSON")
267+
return true
268+
}
269+
}

0 commit comments

Comments
 (0)