forked from TrueCloudLab/certificates
Merge branch 'master' into linkedca
This commit is contained in:
commit
42fde8ba28
27 changed files with 490 additions and 47 deletions
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
9
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Ask on Discord
|
||||||
|
url: https://discord.gg/7xgjhVAg6g
|
||||||
|
about: You can ask for help here!
|
||||||
|
- name: Want to contribute to step certificates?
|
||||||
|
url: https://github.com/smallstep/certificates/blob/master/docs/CONTRIBUTING.md
|
||||||
|
about: Be sure to read contributing guidelines!
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/enhancement.md
vendored
2
.github/ISSUE_TEMPLATE/enhancement.md
vendored
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
name: Certificates Enhancement
|
name: Enhancement
|
||||||
about: Suggest an enhancement to step certificates
|
about: Suggest an enhancement to step certificates
|
||||||
title: ''
|
title: ''
|
||||||
labels: enhancement, needs triage
|
labels: enhancement, needs triage
|
||||||
|
|
10
.github/workflows/labeler.yml
vendored
10
.github/workflows/labeler.yml
vendored
|
@ -1,14 +1,12 @@
|
||||||
name: labeler
|
name: Pull Request Labeler
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request_target
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
label:
|
label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v3
|
- uses: actions/labeler@v3.0.2
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
configuration-path: .github/labeler.yml
|
|
||||||
|
|
14
README.md
14
README.md
|
@ -18,7 +18,14 @@ You can use it to:
|
||||||
|
|
||||||
Whatever your use case, `step-ca` is easy to use and hard to misuse, thanks to [safe, sane defaults](https://smallstep.com/docs/step-ca/certificate-authority-server-production#sane-cryptographic-defaults).
|
Whatever your use case, `step-ca` is easy to use and hard to misuse, thanks to [safe, sane defaults](https://smallstep.com/docs/step-ca/certificate-authority-server-production#sane-cryptographic-defaults).
|
||||||
|
|
||||||
**Questions? Find us in [Discussions](https://github.com/smallstep/certificates/discussions).**
|
---
|
||||||
|
|
||||||
|
**Don't want to run your own CA?**
|
||||||
|
To get up and running quickly, or as an alternative to running your own `step-ca` server, consider creating a [free hosted smallstep Certificate Manager authority](https://info.smallstep.com/certificate-manager-early-access-mvp/).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Questions? Find us in [Discussions](https://github.com/smallstep/certificates/discussions) or [Join our Discord](https://bit.ly/step-discord).**
|
||||||
|
|
||||||
[Website](https://smallstep.com/certificates) |
|
[Website](https://smallstep.com/certificates) |
|
||||||
[Documentation](https://smallstep.com/docs) |
|
[Documentation](https://smallstep.com/docs) |
|
||||||
|
@ -27,7 +34,6 @@ Whatever your use case, `step-ca` is easy to use and hard to misuse, thanks to [
|
||||||
[Contributor's Guide](./docs/CONTRIBUTING.md)
|
[Contributor's Guide](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
[](https://github.com/smallstep/certificates/releases/latest)
|
[](https://github.com/smallstep/certificates/releases/latest)
|
||||||
[](https://microbadger.com/images/smallstep/step-ca)
|
|
||||||
[](https://goreportcard.com/report/github.com/smallstep/certificates)
|
[](https://goreportcard.com/report/github.com/smallstep/certificates)
|
||||||
[](https://travis-ci.com/smallstep/certificates)
|
[](https://travis-ci.com/smallstep/certificates)
|
||||||
[](https://opensource.org/licenses/Apache-2.0)
|
[](https://opensource.org/licenses/Apache-2.0)
|
||||||
|
@ -58,10 +64,10 @@ You can issue certificates in exchange for:
|
||||||
- ID tokens from Okta, GSuite, Azure AD, Auth0.
|
- ID tokens from Okta, GSuite, Azure AD, Auth0.
|
||||||
- ID tokens from an OAuth OIDC service that you host, like [Keycloak](https://www.keycloak.org/) or [Dex](https://github.com/dexidp/dex)
|
- ID tokens from an OAuth OIDC service that you host, like [Keycloak](https://www.keycloak.org/) or [Dex](https://github.com/dexidp/dex)
|
||||||
- [Cloud instance identity documents](https://smallstep.com/blog/embarrassingly-easy-certificates-on-aws-azure-gcp/), for VMs on AWS, GCP, and Azure
|
- [Cloud instance identity documents](https://smallstep.com/blog/embarrassingly-easy-certificates-on-aws-azure-gcp/), for VMs on AWS, GCP, and Azure
|
||||||
- [Single-use, short-lived JWK tokens]() issued by your CD tool — Puppet, Chef, Ansible, Terraform, etc.
|
- [Single-use, short-lived JWK tokens](https://smallstep.com/docs/step-ca/provisioners#jwk) issued by your CD tool — Puppet, Chef, Ansible, Terraform, etc.
|
||||||
- A trusted X.509 certificate (X5C provisioner)
|
- A trusted X.509 certificate (X5C provisioner)
|
||||||
- Expiring SSH host certificates needing rotation (the SSHPOP provisioner)
|
- Expiring SSH host certificates needing rotation (the SSHPOP provisioner)
|
||||||
- Learn more in our [provisioner documentation](https://smallstep.com/docs/step-ca/configuration#jwk)
|
- Learn more in our [provisioner documentation](https://smallstep.com/docs/step-ca/provisioners)
|
||||||
|
|
||||||
### 🏔 Your own private ACME server
|
### 🏔 Your own private ACME server
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,13 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -114,6 +116,17 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tlsAlert(err error) uint8 {
|
||||||
|
var opErr *net.OpError
|
||||||
|
if errors.As(err, &opErr) {
|
||||||
|
v := reflect.ValueOf(opErr.Err)
|
||||||
|
if v.Kind() == reflect.Uint8 {
|
||||||
|
return uint8(v.Uint())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error {
|
func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error {
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
NextProtos: []string{"acme-tls/1"},
|
NextProtos: []string{"acme-tls/1"},
|
||||||
|
@ -129,6 +142,14 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
|
||||||
|
|
||||||
conn, err := vo.TLSDial("tcp", hostPort, config)
|
conn, err := vo.TLSDial("tcp", hostPort, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// With Go 1.17+ tls.Dial fails if there's no overlap between configured
|
||||||
|
// client and server protocols. When this happens the connection is
|
||||||
|
// closed with the error no_application_protocol(120) as required by
|
||||||
|
// RFC7301. See https://golang.org/doc/go1.17#ALPN
|
||||||
|
if tlsAlert(err) == 120 {
|
||||||
|
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
|
||||||
|
"cannot negotiate ALPN acme-tls/1 protocol for tls-alpn-01 challenge"))
|
||||||
|
}
|
||||||
return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err,
|
return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err,
|
||||||
"error doing TLS dial for %s", hostPort))
|
"error doing TLS dial for %s", hostPort))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1395,7 +1395,7 @@ func TestTLSALPN01Validate(t *testing.T) {
|
||||||
assert.Equals(t, updch.Type, ch.Type)
|
assert.Equals(t, updch.Type, ch.Type)
|
||||||
assert.Equals(t, updch.Value, ch.Value)
|
assert.Equals(t, updch.Value, ch.Value)
|
||||||
|
|
||||||
err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: tls: DialWithDialer timed out", ch.Value)
|
err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443:", ch.Value)
|
||||||
|
|
||||||
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
|
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
|
||||||
assert.Equals(t, updch.Error.Type, err.Type)
|
assert.Equals(t, updch.Error.Type, err.Type)
|
||||||
|
|
|
@ -124,10 +124,8 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...st
|
||||||
ordersByAccountMux.Lock()
|
ordersByAccountMux.Lock()
|
||||||
defer ordersByAccountMux.Unlock()
|
defer ordersByAccountMux.Unlock()
|
||||||
|
|
||||||
|
var oldOids []string
|
||||||
b, err := db.db.Get(ordersByAccountIDTable, []byte(accID))
|
b, err := db.db.Get(ordersByAccountIDTable, []byte(accID))
|
||||||
var (
|
|
||||||
oldOids []string
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !nosql.IsErrNotFound(err) {
|
if !nosql.IsErrNotFound(err) {
|
||||||
return nil, errors.Wrapf(err, "error loading orderIDs for account %s", accID)
|
return nil, errors.Wrapf(err, "error loading orderIDs for account %s", accID)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/smallstep/certificates/acme"
|
"github.com/smallstep/certificates/acme"
|
||||||
"github.com/smallstep/certificates/db"
|
"github.com/smallstep/certificates/db"
|
||||||
"github.com/smallstep/nosql"
|
"github.com/smallstep/nosql"
|
||||||
|
"github.com/smallstep/nosql/database"
|
||||||
nosqldb "github.com/smallstep/nosql/database"
|
nosqldb "github.com/smallstep/nosql/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -710,6 +711,34 @@ func TestDB_updateAddOrderIDs(t *testing.T) {
|
||||||
err: errors.Errorf("error saving orderIDs index for account %s", accID),
|
err: errors.Errorf("error saving orderIDs index for account %s", accID),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ok/no-old": func(t *testing.T) test {
|
||||||
|
return test{
|
||||||
|
db: &db.MockNoSQLDB{
|
||||||
|
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||||
|
switch string(bucket) {
|
||||||
|
case string(ordersByAccountIDTable):
|
||||||
|
return nil, database.ErrNotFound
|
||||||
|
default:
|
||||||
|
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
|
||||||
|
return nil, errors.New("force")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) {
|
||||||
|
switch string(bucket) {
|
||||||
|
case string(ordersByAccountIDTable):
|
||||||
|
assert.Equals(t, key, []byte(accID))
|
||||||
|
assert.Equals(t, old, nil)
|
||||||
|
assert.Equals(t, nu, nil)
|
||||||
|
return nil, true, nil
|
||||||
|
default:
|
||||||
|
assert.FatalError(t, errors.Errorf("unexpected bucket %s", string(bucket)))
|
||||||
|
return nil, false, errors.New("force")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: []string{},
|
||||||
|
}
|
||||||
|
},
|
||||||
"ok/all-old-not-pending": func(t *testing.T) test {
|
"ok/all-old-not-pending": func(t *testing.T) test {
|
||||||
oldOids := []string{"foo", "bar"}
|
oldOids := []string{"foo", "bar"}
|
||||||
bOldOids, err := json.Marshal(oldOids)
|
bOldOids, err := json.Marshal(oldOids)
|
||||||
|
|
|
@ -400,7 +400,7 @@ func logOtt(w http.ResponseWriter, token string) {
|
||||||
func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
||||||
if rl, ok := w.(logging.ResponseLogger); ok {
|
if rl, ok := w.(logging.ResponseLogger); ok {
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
"serial": cert.SerialNumber,
|
"serial": cert.SerialNumber.String(),
|
||||||
"subject": cert.Subject.CommonName,
|
"subject": cert.Subject.CommonName,
|
||||||
"issuer": cert.Issuer.CommonName,
|
"issuer": cert.Issuer.CommonName,
|
||||||
"valid-from": cert.NotBefore.Format(time.RFC3339),
|
"valid-from": cert.NotBefore.Format(time.RFC3339),
|
||||||
|
@ -418,7 +418,7 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
||||||
if len(val.CredentialID) > 0 {
|
if len(val.CredentialID) > 0 {
|
||||||
m["provisioner"] = fmt.Sprintf("%s (%s)", val.Name, val.CredentialID)
|
m["provisioner"] = fmt.Sprintf("%s (%s)", val.Name, val.CredentialID)
|
||||||
} else {
|
} else {
|
||||||
m["provisioner"] = val.Name
|
m["provisioner"] = string(val.Name)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,9 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseToken stores the token to protect against reuse.
|
// UseToken stores the token to protect against reuse.
|
||||||
|
//
|
||||||
|
// This method currently ignores any error coming from the GetTokenID, but it
|
||||||
|
// should specifically ignore the error provisioner.ErrAllowTokenReuse.
|
||||||
func (a *Authority) UseToken(token string, prov provisioner.Interface) error {
|
func (a *Authority) UseToken(token string, prov provisioner.Interface) error {
|
||||||
if reuseKey, err := prov.GetTokenID(token); err == nil {
|
if reuseKey, err := prov.GetTokenID(token); err == nil {
|
||||||
if reuseKey == "" {
|
if reuseKey == "" {
|
||||||
|
|
|
@ -131,9 +131,10 @@ func (p *Azure) GetTokenID(token string) (string, error) {
|
||||||
return "", errors.Wrap(err, "error verifying claims")
|
return "", errors.Wrap(err, "error verifying claims")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If TOFU is disabled create return the token kid
|
// If TOFU is disabled then allow token re-use. Azure caches the token for
|
||||||
|
// 24h and without allowing the re-use we cannot use it twice.
|
||||||
if p.DisableTrustOnFirstUse {
|
if p.DisableTrustOnFirstUse {
|
||||||
return claims.ID, nil
|
return "", ErrAllowTokenReuse
|
||||||
}
|
}
|
||||||
|
|
||||||
sum := sha256.Sum256([]byte(claims.XMSMirID))
|
sum := sha256.Sum256([]byte(claims.XMSMirID))
|
||||||
|
|
|
@ -72,7 +72,7 @@ func TestAzure_GetTokenID(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", p1, args{t1}, w1, false},
|
{"ok", p1, args{t1}, w1, false},
|
||||||
{"ok no TOFU", p2, args{t2}, "the-jti", false},
|
{"ok no TOFU", p2, args{t2}, "", true},
|
||||||
{"fail token", p1, args{"bad-token"}, "", true},
|
{"fail token", p1, args{"bad-token"}, "", true},
|
||||||
{"fail claims", p1, args{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey.fooo"}, "", true},
|
{"fail claims", p1, args{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey.fooo"}, "", true},
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
stderrors "errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -32,6 +33,17 @@ type Interface interface {
|
||||||
AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error)
|
AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrAllowTokenReuse is an error that is returned by provisioners that allows
|
||||||
|
// the reuse of tokens.
|
||||||
|
//
|
||||||
|
// This is, for example, returned by the Azure provisioner when
|
||||||
|
// DisableTrustOnFirstUse is set to true. Azure caches tokens for up to 24hr and
|
||||||
|
// has no mechanism for getting a different token - this can be an issue when
|
||||||
|
// rebooting a VM. In contrast, AWS and GCP have facilities for requesting a new
|
||||||
|
// token. Therefore, for the Azure provisioner we are enabling token reuse, with
|
||||||
|
// the understanding that we are not following security best practices
|
||||||
|
var ErrAllowTokenReuse = stderrors.New("allow token reuse")
|
||||||
|
|
||||||
// Audiences stores all supported audiences by request type.
|
// Audiences stores all supported audiences by request type.
|
||||||
type Audiences struct {
|
type Audiences struct {
|
||||||
Sign []string
|
Sign []string
|
||||||
|
|
|
@ -383,7 +383,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
|
||||||
}
|
}
|
||||||
rci.ProvisionerID = p.GetID()
|
rci.ProvisionerID = p.GetID()
|
||||||
rci.TokenID, err = p.GetTokenID(revokeOpts.OTT)
|
rci.TokenID, err = p.GetTokenID(revokeOpts.OTT)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, provisioner.ErrAllowTokenReuse) {
|
||||||
return errs.Wrap(http.StatusInternalServerError, err,
|
return errs.Wrap(http.StatusInternalServerError, err,
|
||||||
"authority.Revoke; could not get ID for token")
|
"authority.Revoke; could not get ID for token")
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,4 +24,7 @@ VOLUME ["/home/step"]
|
||||||
STOPSIGNAL SIGTERM
|
STOPSIGNAL SIGTERM
|
||||||
HEALTHCHECK CMD step ca health 2>/dev/null | grep "^ok" >/dev/null
|
HEALTHCHECK CMD step ca health 2>/dev/null | grep "^ok" >/dev/null
|
||||||
|
|
||||||
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
||||||
CMD exec /usr/local/bin/step-ca --password-file $PWDPATH $CONFIGPATH
|
CMD exec /usr/local/bin/step-ca --password-file $PWDPATH $CONFIGPATH
|
||||||
|
|
60
docker/entrypoint.sh
Normal file
60
docker/entrypoint.sh
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# Paraphrased from:
|
||||||
|
# https://github.com/influxdata/influxdata-docker/blob/0d341f18067c4652dfa8df7dcb24d69bf707363d/influxdb/2.0/entrypoint.sh
|
||||||
|
# (a repo with no LICENSE.md)
|
||||||
|
|
||||||
|
export STEPPATH=$(step path)
|
||||||
|
|
||||||
|
# List of env vars required for step ca init
|
||||||
|
declare -ra REQUIRED_INIT_VARS=(DOCKER_STEPCA_INIT_NAME DOCKER_STEPCA_INIT_DNS_NAMES)
|
||||||
|
|
||||||
|
# Ensure all env vars required to run step ca init are set.
|
||||||
|
function init_if_possible () {
|
||||||
|
local missing_vars=0
|
||||||
|
for var in "${REQUIRED_INIT_VARS[@]}"; do
|
||||||
|
if [ -z "${!var}" ]; then
|
||||||
|
missing_vars=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ ${missing_vars} = 1 ]; then
|
||||||
|
>&2 echo "there is no ca.json config file; please run step ca init, or provide config parameters via DOCKER_STEPCA_INIT_ vars"
|
||||||
|
else
|
||||||
|
step_ca_init "${@}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate_password () {
|
||||||
|
set +o pipefail
|
||||||
|
< /dev/urandom tr -dc A-Za-z0-9 | head -c40
|
||||||
|
echo
|
||||||
|
set -o pipefail
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize a CA if not already initialized
|
||||||
|
function step_ca_init () {
|
||||||
|
local -a setup_args=(
|
||||||
|
--name "${DOCKER_STEPCA_INIT_NAME}"
|
||||||
|
--dns "${DOCKER_STEPCA_INIT_DNS_NAMES}"
|
||||||
|
--provisioner "${DOCKER_STEPCA_INIT_PROVISIONER_NAME:-admin}"
|
||||||
|
--password-file "${STEPPATH}/password"
|
||||||
|
--address ":9000"
|
||||||
|
)
|
||||||
|
if [ -n "${DOCKER_STEPCA_INIT_PASSWORD}" ]; then
|
||||||
|
echo "${DOCKER_STEPCA_INIT_PASSWORD}" > "${STEPPATH}/password"
|
||||||
|
else
|
||||||
|
generate_password > "${STEPPATH}/password"
|
||||||
|
fi
|
||||||
|
if [ -n "${DOCKER_STEPCA_INIT_SSH}" ]; then
|
||||||
|
setup_args=("${setup_args[@]}" --ssh)
|
||||||
|
fi
|
||||||
|
step ca init "${setup_args[@]}"
|
||||||
|
mv $STEPPATH/password $PWDPATH
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f "${STEPPATH}/config/ca.json" ]; then
|
||||||
|
init_if_possible
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "${@}"
|
|
@ -7,12 +7,20 @@ to manage issues, etc.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
* [Building From Source](#building-from-source)
|
- [Contributing to `step certificates`](#contributing-to-step-certificates)
|
||||||
* [Asking Support Questions](#asking-support-questions)
|
- [Table of Contents](#table-of-contents)
|
||||||
* [Reporting Issues](#reporting-issues)
|
- [Building From Source](#building-from-source)
|
||||||
* [Submitting Patches](#submitting-patches)
|
- [Build a standard `step-ca`](#build-a-standard-step-ca)
|
||||||
* [Code Contribution Guidelines](#code-contribution-guidelines)
|
- [Build `step-ca` using CGO](#build-step-ca-using-cgo)
|
||||||
* [Git Commit Message Guidelines](#git-commit-message-guidelines)
|
- [The CGO build enables PKCS #11 and YubiKey PIV support](#the-cgo-build-enables-pkcs-11-and-yubikey-piv-support)
|
||||||
|
- [1. Install PCSC support](#1-install-pcsc-support)
|
||||||
|
- [2. Build `step-ca`](#2-build-step-ca)
|
||||||
|
- [Asking Support Questions](#asking-support-questions)
|
||||||
|
- [Reporting Issues](#reporting-issues)
|
||||||
|
- [Code Contribution](#code-contribution)
|
||||||
|
- [Submitting Patches](#submitting-patches)
|
||||||
|
- [Code Contribution Guidelines](#code-contribution-guidelines)
|
||||||
|
- [Git Commit Message Guidelines](#git-commit-message-guidelines)
|
||||||
|
|
||||||
## Building From Source
|
## Building From Source
|
||||||
|
|
||||||
|
@ -73,7 +81,7 @@ When the build is complete, you will find binaries in `bin/`.
|
||||||
|
|
||||||
## Asking Support Questions
|
## Asking Support Questions
|
||||||
|
|
||||||
Feel free to post a question on our [GitHub Discussions](https://github.com/smallstep/certificates/discussions) page, or find us on [Gitter](https://gitter.im/smallstep/community).
|
Feel free to post a question on our [GitHub Discussions](https://github.com/smallstep/certificates/discussions) page, or find us on [Discord](https://bit.ly/step-discord).
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Provisioners
|
# Provisioners
|
||||||
|
|
||||||
> Note: The canonical documentation for `step-ca` provisioners now lives at
|
> Note: The canonical documentation for `step-ca` provisioners now lives at
|
||||||
> https://smallstep.com/docs/step-ca/configuration#provisioners. Documentation
|
> https://smallstep.com/docs/step-ca/provisioners. Documentation
|
||||||
> found on this page may be out of date.
|
> found on this page may be out of date.
|
||||||
|
|
||||||
Provisioners are people or code that are registered with the CA and authorized
|
Provisioners are people or code that are registered with the CA and authorized
|
||||||
|
|
|
@ -202,7 +202,8 @@ through an example.
|
||||||
[Use TLS Everywhere](https://smallstep.com/blog/use-tls.html) and let us know
|
[Use TLS Everywhere](https://smallstep.com/blog/use-tls.html) and let us know
|
||||||
what you think of our tools. Get in touch over
|
what you think of our tools. Get in touch over
|
||||||
[Twitter](twitter.com/smallsteplabs) or through our
|
[Twitter](twitter.com/smallsteplabs) or through our
|
||||||
[GitHub Discussions](https://github.com/smallstep/certificates/discussions) to chat with us in real time.
|
[GitHub Discussions](https://github.com/smallstep/certificates/discussions) to find answers to frequently asked questions.
|
||||||
|
[Discord](https://bit.ly/step-discord) to chat with us in real time.
|
||||||
|
|
||||||
## Further Reading
|
## Further Reading
|
||||||
|
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -7,6 +7,7 @@ require (
|
||||||
github.com/Masterminds/sprig/v3 v3.1.0
|
github.com/Masterminds/sprig/v3 v3.1.0
|
||||||
github.com/ThalesIgnite/crypto11 v1.2.4
|
github.com/ThalesIgnite/crypto11 v1.2.4
|
||||||
github.com/aws/aws-sdk-go v1.30.29
|
github.com/aws/aws-sdk-go v1.30.29
|
||||||
|
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd // indirect
|
||||||
github.com/go-chi/chi v4.0.2+incompatible
|
github.com/go-chi/chi v4.0.2+incompatible
|
||||||
github.com/go-kit/kit v0.10.0 // indirect
|
github.com/go-kit/kit v0.10.0 // indirect
|
||||||
github.com/go-piv/piv-go v1.7.0
|
github.com/go-piv/piv-go v1.7.0
|
||||||
|
@ -22,7 +23,7 @@ require (
|
||||||
github.com/rs/xid v1.2.1
|
github.com/rs/xid v1.2.1
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
|
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
|
||||||
github.com/smallstep/nosql v0.3.6
|
github.com/smallstep/nosql v0.3.8
|
||||||
github.com/stretchr/testify v1.7.0 // indirect
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
github.com/urfave/cli v1.22.4
|
github.com/urfave/cli v1.22.4
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1
|
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1
|
||||||
|
|
13
go.sum
13
go.sum
|
@ -136,9 +136,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
|
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
|
||||||
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
|
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
|
||||||
github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658 h1:/WBjuutuivOA02gpDtrvrWKw01ugkyt3QnimB7enbtI=
|
github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
|
||||||
github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658/go.mod h1:2uGEvGm+JSDLd5UAaKIFSbXDcYyeH0fWJP4N2HMMYMI=
|
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
|
||||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||||
|
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||||
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc=
|
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc=
|
||||||
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA=
|
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
@ -226,8 +227,6 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
@ -328,6 +327,8 @@ github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
|
||||||
|
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
@ -462,8 +463,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||||
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
|
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
|
||||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
||||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||||
github.com/smallstep/nosql v0.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po=
|
github.com/smallstep/nosql v0.3.8 h1:1/EWUbbEdz9ai0g9Fd09VekVjtxp+5+gIHpV2PdwW3o=
|
||||||
github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ=
|
github.com/smallstep/nosql v0.3.8/go.mod h1:X2qkYpNcW3yjLUvhEHfgGfClpKbFPapewvx7zo4TOFs=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
|
|
@ -46,8 +46,8 @@ var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{
|
||||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
||||||
},
|
},
|
||||||
apiv1.SHA512WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
apiv1.SHA512WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512,
|
||||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512,
|
||||||
},
|
},
|
||||||
apiv1.SHA256WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
apiv1.SHA256WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
|
0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
|
||||||
|
|
|
@ -59,7 +59,9 @@ func Parse(rawuri string) (*URI, error) {
|
||||||
if u.Scheme == "" {
|
if u.Scheme == "" {
|
||||||
return nil, errors.Errorf("error parsing %s: scheme is missing", rawuri)
|
return nil, errors.Errorf("error parsing %s: scheme is missing", rawuri)
|
||||||
}
|
}
|
||||||
v, err := url.ParseQuery(u.Opaque)
|
// Starting with Go 1.17 url.ParseQuery returns an error using semicolon as
|
||||||
|
// separator.
|
||||||
|
v, err := url.ParseQuery(strings.ReplaceAll(u.Opaque, ";", "&"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error parsing %s", rawuri)
|
return nil, errors.Wrapf(err, "error parsing %s", rawuri)
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,3 +274,28 @@ func TestURI_Pin(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestURI_String(t *testing.T) {
|
||||||
|
mustParse := func(s string) *URI {
|
||||||
|
u, err := Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uri *URI
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"ok new", New("yubikey", url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}}), "yubikey:foo=bar;slot-id=9a"},
|
||||||
|
{"ok parse", mustParse("yubikey:slot-id=9a;foo=bar?bar=zar"), "yubikey:slot-id=9a;foo=bar?bar=zar"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.uri.String(); got != tt.want {
|
||||||
|
t.Errorf("URI.String() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
scripts/README.md
Normal file
4
scripts/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Scripts folder
|
||||||
|
|
||||||
|
Please note that `install-step-ra.sh` is referenced on the `files.smallstep.com` S3 website bucket as a redirect to `raw.githubusercontent.com`. If you move it, please update the S3 redirect.
|
||||||
|
|
265
scripts/install-step-ra.sh
Normal file
265
scripts/install-step-ra.sh
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# - Parse params using argbash (argbash.io). Here's a template that I have tested but have not implemented yet:
|
||||||
|
#
|
||||||
|
# ARG_OPTIONAL_SINGLE([ca-url], , [the URL of the upstream (issuing) step-ca server])
|
||||||
|
# ARG_OPTIONAL_SINGLE([fingerprint], , [the SHA256 fingerprint of the upstream peer step-ca server])
|
||||||
|
# ARG_OPTIONAL_SINGLE([provisioner-name], , [the name of a JWK provisioner on the upstream CA that this RA will use])
|
||||||
|
# ARG_OPTIONAL_SINGLE([provisioner-password-file], , [the name a file containing the upstream JWK provisioner password])
|
||||||
|
# ARG_OPTIONAL_REPEATED([dns-name], , [DNS name of this RA that will appear on its TLS certificate; you may pass this flag multiple times])
|
||||||
|
# ARG_OPTIONAL_SINGLE([listen-address], , [the address (and port #) this RA will listen on, eg. :443 or 127.0.0.1:4443])
|
||||||
|
# ARG_HELP([This script will install and configure a Registration Authority that connects to an upstream CA running step-ca.])
|
||||||
|
# ARGBASH_GO
|
||||||
|
|
||||||
|
echo "This script will install and start a step-ca server running in Registration Authority (RA) mode."
|
||||||
|
echo ""
|
||||||
|
echo "You will need an upstream CA (URL and fingerprint)"
|
||||||
|
echo "Don't have a CA? Sign up for a hosted CA at smallstep.com — or run your own."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Fail if this script is not run as root.
|
||||||
|
if ! [ $(id -u) = 0 ]; then
|
||||||
|
echo "This script must be run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Architecture detection
|
||||||
|
arch=$(uname -m)
|
||||||
|
case $arch in
|
||||||
|
x86_64) arch="amd64" ;;
|
||||||
|
x86) arch="386" ;;
|
||||||
|
i686) arch="386" ;;
|
||||||
|
i386) arch="386" ;;
|
||||||
|
aarch64) arch="arm64" ;;
|
||||||
|
armv5*) arch="armv5" ;;
|
||||||
|
armv6*) arch="armv6" ;;
|
||||||
|
armv7*) arch="armv7" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$arch" = "armv5" ]; then
|
||||||
|
echo "This script doesn't work on armv5 machines"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! hash jq &> /dev/null; then
|
||||||
|
echo "This script requires the jq commmand; please install it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! hash curl &> /dev/null; then
|
||||||
|
echo "This script requires the curl commmand; please install it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! hash tar &> /dev/null; then
|
||||||
|
echo "This script requires the tar commmand; please install it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--ca-url)
|
||||||
|
CA_URL="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--fingerprint)
|
||||||
|
CA_FINGERPRINT="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--provisioner-name)
|
||||||
|
CA_PROVISIONER_NAME="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--provisioner-password-file)
|
||||||
|
CA_PROVISIONER_JWK_PASSWORD_FILE="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--dns-names)
|
||||||
|
RA_DNS_NAMES="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--listen-address)
|
||||||
|
RA_ADDRESS="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Install step
|
||||||
|
if ! hash step &> /dev/null; then
|
||||||
|
echo "Installing 'step' in /usr/bin..."
|
||||||
|
STEP_VERSION=$(curl -s https://api.github.com/repos/smallstep/cli/releases/latest | jq -r '.tag_name')
|
||||||
|
|
||||||
|
curl -sLO https://github.com/smallstep/cli/releases/download/$STEP_VERSION/step_linux_${STEP_VERSION:1}_$arch.tar.gz
|
||||||
|
tar xvzf step_linux_${STEP_VERSION:1}_$arch.tar.gz
|
||||||
|
install -m 0755 -t /usr/bin step_${STEP_VERSION:1}/bin/step
|
||||||
|
|
||||||
|
rm step_linux_${STEP_VERSION:1}_$arch.tar.gz
|
||||||
|
rm -rf step_${STEP_VERSION:1}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prompt for required parameters
|
||||||
|
if [ -z "$CA_URL" ]; then
|
||||||
|
CA_URL=""
|
||||||
|
while [[ $CA_URL = "" ]]; do
|
||||||
|
read -p "Issuing CA URL: " CA_URL < /dev/tty
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$CA_FINGERPRINT" ]; then
|
||||||
|
CA_FINGERPRINT=""
|
||||||
|
while [[ $CA_FINGERPRINT = "" ]]; do
|
||||||
|
read -p "Issuing CA Fingerprint: " CA_FINGERPRINT < /dev/tty
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Bootstrapping with the CA..."
|
||||||
|
export STEPPATH=$(mktemp -d)
|
||||||
|
export STEP_CONSOLE=true
|
||||||
|
|
||||||
|
step ca bootstrap --ca-url $CA_URL --fingerprint $CA_FINGERPRINT
|
||||||
|
|
||||||
|
if [ -z "$CA_PROVISIONER_NAME" ]; then
|
||||||
|
declare -a provisioners
|
||||||
|
readarray -t provisioners < <(step ca provisioner list | jq -r '.[] | select(.type == "JWK") | .name')
|
||||||
|
provisioners+=("Create provisioner")
|
||||||
|
printf '%s\n' "${provisioners[@]}"
|
||||||
|
|
||||||
|
printf "%b" "\nSelect a JWK provisioner:\n" >&2
|
||||||
|
select provisioner in "${provisioners[@]}"; do
|
||||||
|
if [ "$provisioner" == "Create provisioner" ]; then
|
||||||
|
echo "Creating a JWK provisioner on the upstream CA..."
|
||||||
|
echo ""
|
||||||
|
read -p "Label your provisioner (e.g. example-ra): " CA_PROVISIONER_NAME < /dev/tty
|
||||||
|
step beta ca provisioner add $CA_PROVISIONER_NAME --type JWK --create
|
||||||
|
break
|
||||||
|
elif [ -n "$provisioner" ]; then
|
||||||
|
echo "Using existing provisioner $provisioner."
|
||||||
|
CA_PROVISIONER_NAME=$provisioner
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "Invalid selection!"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RA_DNS_NAMES" ]; then
|
||||||
|
RA_DNS_NAMES=""
|
||||||
|
while [[ $RA_DNS_NAMES = "" ]]; do
|
||||||
|
echo "What DNS names or IP addresses will your RA use?"
|
||||||
|
read -p "(e.g. acme.example.com[,1.1.1.1,etc.]): " RA_DNS_NAMES < /dev/tty
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RA_ADDRESS" ]; then
|
||||||
|
RA_ADDRESS=""
|
||||||
|
while [[ $RA_ADDRESS = "" ]] ; do
|
||||||
|
echo "What address should your RA listen on?"
|
||||||
|
read -p "(e.g. :443 or 10.2.1.201:4430): " RA_ADDRESS < /dev/tty
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$CA_PROVISIONER_JWK_PASSWORD_FILE" ]; then
|
||||||
|
read -s -p "Enter the CA Provisioner Password: " CA_PROVISIONER_JWK_PASSWORD < /dev/tty
|
||||||
|
printf "%b" "\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing 'step-ca' in /usr/bin..."
|
||||||
|
CA_VERSION=$(curl -s https://api.github.com/repos/smallstep/certificates/releases/latest | jq -r '.tag_name')
|
||||||
|
|
||||||
|
curl -sLO https://github.com/smallstep/certificates/releases/download/$CA_VERSION/step-ca_linux_${CA_VERSION:1}_$arch.tar.gz
|
||||||
|
tar -xf step-ca_linux_${CA_VERSION:1}_$arch.tar.gz
|
||||||
|
install -m 0755 -t /usr/bin step-ca_${CA_VERSION:1}/bin/step-ca
|
||||||
|
setcap CAP_NET_BIND_SERVICE=+eip $(which step-ca)
|
||||||
|
rm step-ca_linux_${CA_VERSION:1}_$arch.tar.gz
|
||||||
|
rm -rf step-ca_${CA_VERSION:1}
|
||||||
|
|
||||||
|
echo "Creating 'step' user..."
|
||||||
|
export STEPPATH=/etc/step-ca
|
||||||
|
|
||||||
|
useradd --system --home $(step path) --shell /bin/false step
|
||||||
|
|
||||||
|
echo "Creating RA configuration..."
|
||||||
|
mkdir -p $(step path)/db
|
||||||
|
mkdir -p $(step path)/config
|
||||||
|
|
||||||
|
cat <<EOF > $(step path)/config/ca.json
|
||||||
|
{
|
||||||
|
"address": "$RA_ADDRESS",
|
||||||
|
"dnsNames": ["$RA_DNS_NAMES"],
|
||||||
|
"db": {
|
||||||
|
"type": "badgerV2",
|
||||||
|
"dataSource": "/etc/step-ca/db"
|
||||||
|
},
|
||||||
|
"logger": {"format": "text"},
|
||||||
|
"authority": {
|
||||||
|
"type": "stepcas",
|
||||||
|
"certificateAuthority": "$CA_URL",
|
||||||
|
"certificateAuthorityFingerprint": "$CA_FINGERPRINT",
|
||||||
|
"certificateIssuer": {
|
||||||
|
"type" : "jwk",
|
||||||
|
"provisioner": "$CA_PROVISIONER_NAME"
|
||||||
|
},
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "ACME",
|
||||||
|
"name": "acme"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"cipherSuites": [
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
|
||||||
|
],
|
||||||
|
"minVersion": 1.2,
|
||||||
|
"maxVersion": 1.3,
|
||||||
|
"renegotiation": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if ! [ -z "$CA_PROVISIONER_JWK_PASSWORD" ]; then
|
||||||
|
echo "Saving provisoiner password to $(step path)/password.txt..."
|
||||||
|
echo $CA_PROVISIONER_JWK_PASSWORD > $(step path)/password.txt
|
||||||
|
else
|
||||||
|
echo "Copying provisioner password file to $(step path)/password.txt..."
|
||||||
|
cp $CA_PROVISIONER_JWK_PASSWORD_FILE $(step path)/password.txt
|
||||||
|
fi
|
||||||
|
chmod 440 $(step path)/password.txt
|
||||||
|
|
||||||
|
# Add a service to systemd for the RA.
|
||||||
|
echo "Creating systemd service step-ca.service..."
|
||||||
|
curl -sL https://raw.githubusercontent.com/smallstep/certificates/master/systemd/step-ca.service \
|
||||||
|
-o /etc/systemd/system/step-ca.service
|
||||||
|
|
||||||
|
echo "Creating RA mode override /etc/systemd/system/step-ca.service.d/local.conf..."
|
||||||
|
mkdir /etc/systemd/system/step-ca.service.d
|
||||||
|
cat <<EOF > /etc/systemd/system/step-ca.service.d/local.conf
|
||||||
|
[Service]
|
||||||
|
; The empty ExecStart= clears the inherited ExecStart= value
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/usr/bin/step-ca config/ca.json --issuer-password-file password.txt
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Starting step-ca.service..."
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
chown -R step:step $(step path)
|
||||||
|
|
||||||
|
systemctl enable --now step-ca
|
||||||
|
|
||||||
|
echo "Adding STEPPATH export to /root/.bash_profile..."
|
||||||
|
echo "export STEPPATH=$STEPPATH" >> /root/.bash_profile
|
||||||
|
|
||||||
|
echo "Finished. Check the journal with journalctl -fu step-ca.service"
|
||||||
|
|
|
@ -12,14 +12,10 @@ Environment=STEPPATH=/etc/step-ca \
|
||||||
CERT_LOCATION=/etc/step/certs/%i.crt \
|
CERT_LOCATION=/etc/step/certs/%i.crt \
|
||||||
KEY_LOCATION=/etc/step/certs/%i.key
|
KEY_LOCATION=/etc/step/certs/%i.key
|
||||||
|
|
||||||
; ExecStartPre checks if the certificate is ready for renewal,
|
; ExecCondition checks if the certificate is ready for renewal,
|
||||||
; based on the exit status of the command.
|
; based on the exit status of the command.
|
||||||
; (In systemd 243 and above, you can use ExecCondition= here.)
|
; (In systemd 242 or below, you can use ExecStartPre= here.)
|
||||||
ExecStartPre=/usr/bin/env bash -c \
|
ExecCondition=/usr/bin/step certificate needs-renewal $CERT_LOCATION
|
||||||
'step certificate inspect $CERT_LOCATION --format json --roots "$STEPPATH/certs/root_ca.crt" | \
|
|
||||||
jq -e "(((.validity.start | fromdate) + \
|
|
||||||
((.validity.end | fromdate) - (.validity.start | fromdate)) * 0.66) \
|
|
||||||
- now) <= 0" > /dev/null'
|
|
||||||
|
|
||||||
; ExecStart renews the certificate, if ExecStartPre was successful.
|
; ExecStart renews the certificate, if ExecStartPre was successful.
|
||||||
ExecStart=/usr/bin/step ca renew --force $CERT_LOCATION $KEY_LOCATION
|
ExecStart=/usr/bin/step ca renew --force $CERT_LOCATION $KEY_LOCATION
|
||||||
|
|
Loading…
Add table
Reference in a new issue