Skip to content

Commit cea9f60

Browse files
authored
Merge 7874ade into 521884a
2 parents 521884a + 7874ade commit cea9f60

30 files changed

Lines changed: 1300 additions & 846 deletions

.github/workflows/keyfactor-workflow.yml renamed to .github/workflows/keyfactor-bootstrap-workflow.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Keyfactor Release Workflow
1+
name: Keyfactor Bootstrap Workflow
22

33
on:
44
workflow_dispatch:
@@ -11,9 +11,10 @@ on:
1111

1212
jobs:
1313
call-starter-workflow:
14-
uses: keyfactor/actions/.github/workflows/starter.yml@v2
14+
uses: keyfactor/actions/.github/workflows/starter.yml@v3
1515
secrets:
1616
token: ${{ secrets.V2BUILDTOKEN}}
1717
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
1818
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
1919
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
20+
scan_token: ${{ secrets.SAST_TOKEN }}

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
with:
5757
deploy-k8s: 'true'
5858
deploy-nginx-ingress: 'true'
59+
deploy-signserver: 'false'
5960

6061
# Run Go tests
6162
- name: Run go test

.golangci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
run:
2+
# timeout for analysis, e.g. 30s, 5m, default is 1m
3+
timeout: 12m
4+
5+
skip-dirs:
6+
- testdata$
7+
- test/mock
8+
9+
skip-files:
10+
- ".*\\.pb\\.go"
11+
12+
linters:
13+
enable:
14+
- bodyclose
15+
- errorlint
16+
- goimports
17+
- revive
18+
- gosec
19+
- misspell
20+
- nakedret
21+
- unconvert
22+
- unparam
23+
- whitespace
24+
- gocritic
25+
- nolintlint
26+
27+
linters-settings:
28+
revive:
29+
# minimal confidence for issues, default is 0.8
30+
confidence: 0.0

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# v1.5.0
2+
## Fixes
3+
* Patched bug that required the `allow_any_name` or `allowed_domains=[""]` role parameters to issue/sign certificates with no CN.
4+
5+
## Chores
6+
* Upgrade from Go `v1.21` to `v1.22`.
7+
* Implement more strict golangci-lint policy.
8+
9+
## Features
10+
* If upstream EJBCA API call fails, the HTTP status code is propogated to the Vault user via the Vault API status code.
11+
* Implement OAuth 2.0 "client credentials" token flow as a supported authentication mechanism to EJBCA.
12+
113
# v1.4.0
214
## Fixes
315
* Paths that need to write to the Storage backend now forward the request to the primary node. This is important in Enterprise HA deployments where Performance Standby Nodes are allowed to handle read requests/paths.

README.md

Lines changed: 23 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,15 @@
11

2-
# EJBCA Vault PKI Secrets Engine
3-
4-
EJBCA PKI Engine and Backend for HashiCorp Vault. Used to issue, sign, and revoke certificates using the EJBCA CA via HashiCorp Vault
5-
6-
#### Integration status: Production - Ready for use in production environments.
7-
8-
## About the Keyfactor API Client
9-
10-
This API client allows for programmatic management of Keyfactor resources.
11-
12-
## Support for EJBCA Vault PKI Secrets Engine
13-
14-
EJBCA Vault PKI Secrets Engine is open source and supported on best effort level for this tool/library/client. This means customers can report Bugs, Feature Requests, Documentation amendment or questions as well as requests for customer information required for setup that needs Keyfactor access to obtain. Such requests do not follow normal SLA commitments for response or resolution. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com/
15-
16-
###### To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab.
17-
18-
---
19-
20-
21-
---
22-
232

3+
# EJBCA Vault PKI Secrets Engine
244

25-
<!--EJBCA Community logo -->
26-
<a href="https://ejbca.org">
27-
<img src=".github/images/community-ejbca.png?raw=true)" alt="EJBCA logo" title="EJBCA" height="70" />
28-
</a>
29-
<!--EJBCA Enterprise logo -->
30-
<a href="https://www.keyfactor.com/products/ejbca-enterprise/">
31-
<img src=".github/images/keyfactor-ejbca-enterprise.png?raw=true)" alt="EJBCA logo" title="EJBCA" height="70" />
32-
</a>
5+
<img src="https://img.shields.io/badge/integration_status-production-3D1973?style=flat-square" alt="Integration Status: production" />
6+
<a href="https://ejbca.org"><img src="https://img.shields.io/badge/valid_for-ejbca_community-FF9371" alt="Valid for EJBCA Community"></a>
7+
<a href="https://www.keyfactor.com/products/ejbca-enterprise/"><img src="https://img.shields.io/badge/valid_for-ejbca_enterprise-5F61FF" alt="Valid for EJBCA Enterprise"></a>
8+
<a href="https://goreportcard.com/report/github.com/keyfactor/ejbca-vault-pki-engine"><img src="https://goreportcard.com/badge/github.com/keyfactor/ejbca-vault-pki-engine" alt="Go Report Card"></a>
339

34-
<!--- Insert the Tool Name in the main heading! --->
35-
# EJBCA PKI Secrets Engine for HashiCorp Vault
3610

37-
[![Go Report Card](https://goreportcard.com/badge/github.com/Keyfactor/ejbca-vault-pki-engine)](https://goreportcard.com/report/github.com/Keyfactor/ejbca-vault-pki-engine)
3811

39-
<!--- Short intro here! --->
40-
<!--- Include a description of the project/repository, the purpose of it, what problems it solves, when to use it (and not use it), etc. --->
12+
## Overview
4113

4214
The EJBCA PKI Secrets Engine for HashiCorp Vault enables DevOps teams to request and retrieve certificates
4315
from EJBCA using HashiCorp Vault, while security teams retain control over backend PKI operations.
@@ -48,49 +20,39 @@ The EJBCA PKI Secrets Engine is a Vault plugin that replicates the built-in Vaul
4820
requests through EJBCA instead of through Vault. The plugin was designed to be swapped for the built-in Vault PKI secrets engine
4921
with minimal changes to existing Vault configurations.
5022

51-
## Get Started
5223

53-
<!--- Insert links to instructions on how to install, configure, etc.
54-
Example from ejbca-cert-manager-issuer below:
5524

56-
* To install the tool, see [Installation](docs/install.md).
57-
* To configure and use the tool, see:
58-
* [Usage](docs/config_usage.md)
59-
* [Customization](docs/annotations.md)
60-
* [End Entity Name Selection](docs/endentitynamecustomization.md)
61-
* To test the tool, see [Testing the Source](docs/testing.md).
62-
--->
25+
## Requirements
6326

64-
To get started with EJBCA PKI Secrets Engine for HashiCorp Vault, see [Getting Started](docs/getting-started.md).
27+
### To build
28+
* [Git](https://git-scm.com/)
29+
* [Golang](https://golang.org/) >= v1.22
6530

66-
### System Requirements
31+
### To use
32+
* [EJBCA](https://www.keyfactor.com/products/ejbca-enterprise/) >= v7.7
33+
* [HashiCorp Vault](https://www.vaultproject.io/) >= v1.11.0
34+
35+
36+
37+
## Getting Started
38+
39+
To get started with EJBCA PKI Secrets Engine for HashiCorp Vault, see [Getting Started](docs/getting-started.md).
6740

68-
<!--- Insert any requirements in this section. --->
69-
To run the EJBCA PKI Secrets Engine for HashiCorp Vault, the EJBCA REST API needs to be set up with certain endpoints. There are also requirements on certain versions of Git, Golang, EJBCA, and HashiCorp Vault.
7041

71-
See the complete list in [System Requirements](docs/getting-started.md#requirements).
7242

7343
## Community Support
74-
In the [Keyfactor Community](https://www.keyfactor.com/community/), we welcome contributions.
7544

76-
The Community software is open-source and community-supported, meaning that **no SLA** is applicable.
45+
In the [Keyfactor Community](https://www.keyfactor.com/community/), we welcome contributions. Keyfactor Community software is open-source and community-supported, meaning that **no SLA** is applicable. Keyfactor will address issues as resources become available.
7746

7847
* To report a problem or suggest a new feature, go to [Issues](../../issues).
79-
* If you want to contribute actual bug fixes or proposed enhancements, see the [Contributing Guidelines](CONTRIBUTING.md) and go to [Pull requests](../../pulls).
48+
* If you want to contribute bug fixes or proposed enhancements, see the [Contributing Guidelines](CONTRIBUTING.md) and create a [Pull request](../../pulls).
8049

8150
## Commercial Support
8251

8352
Commercial support is available for [EJBCA Enterprise](https://www.keyfactor.com/products/ejbca-enterprise/).
8453

85-
<!--- For SignServer, update to the following text and link:
86-
Commercial support is available for [SignServer Enterprise](https://www.keyfactor.com/products/signserver-enterprise/).
87-
--->
88-
8954
## License
90-
<!--- No updates needed --->
91-
For License information, see [LICENSE](LICENSE).
55+
For license information, see [LICENSE](LICENSE).
9256

9357
## Related Projects
94-
See all [Keyfactor EJBCA GitHub projects](https://github.com/orgs/Keyfactor/repositories?q=ejbca).
95-
96-
58+
See all [Keyfactor EJBCA GitHub projects](https://github.com/orgs/Keyfactor/repositories?q=ejbca).

backend.go

Lines changed: 119 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
11
/*
2-
Copyright 2024 Keyfactor
3-
Licensed under the Apache License, Version 2.0 (the "License"); you may
4-
not use this file except in compliance with the License. You may obtain a
5-
copy of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless
6-
required by applicable law or agreed to in writing, software distributed
7-
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8-
OR CONDITIONS OF ANY KIND, either express or implied. See the License for
9-
thespecific language governing permissions and limitations under the
10-
License.
2+
Copyright © 2024 Keyfactor
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
1115
*/
12-
package ejbca_vault_pki_engine
16+
17+
package ejbca
1318

1419
import (
1520
"context"
16-
"math/rand"
21+
"crypto/rand"
22+
"crypto/tls"
23+
"crypto/x509"
24+
"fmt"
25+
"math/big"
1726
"strings"
1827
"sync"
1928

29+
"github.com/Keyfactor/ejbca-go-client-sdk/api/ejbca"
2030
"github.com/hashicorp/vault/sdk/framework"
2131
"github.com/hashicorp/vault/sdk/helper/consts"
32+
"github.com/hashicorp/vault/sdk/helper/errutil"
2233
"github.com/hashicorp/vault/sdk/logical"
2334
)
2435

@@ -96,7 +107,7 @@ func (b *ejbcaBackend) reset() {
96107

97108
// invalidate clears an existing client configuration in
98109
// the backend
99-
func (b *ejbcaBackend) invalidate(ctx context.Context, key string) {
110+
func (b *ejbcaBackend) invalidate(_ context.Context, key string) {
100111
if key == "config" {
101112
b.reset()
102113
}
@@ -130,17 +141,104 @@ func (sc *storageContext) getClient() (*ejbcaClient, error) {
130141
config = new(ejbcaConfig)
131142
}
132143

144+
logger.Trace("Creating new EJBCA authenticator")
145+
authenticator, err := sc.Backend.newAuthenticator(config)
146+
if err != nil {
147+
return nil, err
148+
}
149+
if authenticator == nil {
150+
logger.Error("Authenticator is nil")
151+
return nil, fmt.Errorf("Authenticator is nil")
152+
}
153+
133154
logger.Trace("Creating new EJBCA client")
134-
sc.Backend.client, err = newClient(config)
155+
sdkConfig := ejbca.NewConfiguration()
156+
sdkConfig.Host = config.Hostname
157+
sdkConfig.SetAuthenticator(authenticator)
158+
159+
client, err := ejbca.NewAPIClient(sdkConfig)
135160
if err != nil {
136161
return nil, err
137162
}
163+
sc.Backend.client = &ejbcaClient{client}
138164

139165
return sc.Backend.client, nil
140166
}
141167

168+
func (b *ejbcaBackend) newAuthenticator(config *ejbcaConfig) (ejbca.Authenticator, error) {
169+
var err error
170+
logger := b.Logger().Named("ejbcaBackend.newAuthenticator")
171+
172+
var caChain []*x509.Certificate
173+
if config.CaCert != "" {
174+
logger.Info("CA chain present - Parsing CA chain from configuration")
175+
176+
blocks := decodePEMBytes([]byte(config.CaCert))
177+
if len(blocks) == 0 {
178+
return nil, errutil.UserError{Err: "didn't find pem certificate in ca_cert"}
179+
}
180+
181+
for _, block := range blocks {
182+
// Parse the PEM block into an x509 certificate
183+
cert, err := x509.ParseCertificate(block.Bytes)
184+
if err != nil {
185+
return nil, fmt.Errorf("failed to parse CA certificate: %w", err)
186+
}
187+
188+
caChain = append(caChain, cert)
189+
}
190+
191+
logger.Debug("Parsed CA chain", "length", len(caChain))
192+
}
193+
194+
var authenticator ejbca.Authenticator
195+
switch {
196+
case config.ClientCert != "" && config.ClientKey != "":
197+
logger.Info("Creating mTLS authenticator")
198+
199+
var tlsCert tls.Certificate
200+
tlsCert, err := tls.X509KeyPair([]byte(config.ClientCert), []byte(config.ClientKey))
201+
if err != nil {
202+
return nil, fmt.Errorf("failed to load client certificate: %w", err)
203+
}
204+
205+
authenticator, err = ejbca.NewMTLSAuthenticatorBuilder().
206+
WithCaCertificates(caChain).
207+
WithClientCertificate(&tlsCert).
208+
Build()
209+
if err != nil {
210+
logger.Error("Failed to build mTLS authenticator")
211+
return nil, fmt.Errorf("failed to build MTLS authenticator: %w", err)
212+
}
213+
214+
logger.Info("Created mTLS authenticator")
215+
case config.TokenURL != "" && config.ClientID != "" && config.ClientSecret != "":
216+
logger.Info("Creating OAuth authenticator")
217+
218+
authenticator, err = ejbca.NewOAuthAuthenticatorBuilder().
219+
WithCaCertificates(caChain).
220+
WithTokenUrl(config.TokenURL).
221+
WithClientId(config.ClientID).
222+
WithClientSecret(config.ClientSecret).
223+
WithAudience(config.Audience).
224+
WithScopes(config.Scopes).
225+
Build()
226+
if err != nil {
227+
logger.Error("Failed to build OAuth authenticator")
228+
return nil, fmt.Errorf("failed to build OAuth authenticator: %w", err)
229+
}
230+
231+
logger.Info("Created OAuth authenticator")
232+
default:
233+
logger.Error("no authentication method configured")
234+
return nil, fmt.Errorf("no authentication method configured")
235+
}
236+
237+
return authenticator, nil
238+
}
239+
142240
func (b *ejbcaBackend) isRunningOnPerformanceStandby() bool {
143-
return b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby)
241+
return b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby)
144242
}
145243

146244
// backendHelp should contain help information for the backend
@@ -150,11 +248,15 @@ After mounting this backend, credentials to manage certificates must be configur
150248
with the "config/" endpoints.
151249
`
152250

153-
func generateRandomString(length int) string {
251+
func generateRandomString(length int) (string, error) {
154252
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
155253
b := make([]rune, length)
156254
for i := range b {
157-
b[i] = letters[rand.Intn(len(letters))]
255+
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
256+
if err != nil {
257+
return "", err
258+
}
259+
b[i] = letters[num.Int64()]
158260
}
159-
return string(b)
261+
return string(b), nil
160262
}

0 commit comments

Comments
 (0)