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
|
||||
title: ''
|
||||
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:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
- uses: actions/labeler@v3.0.2
|
||||
with:
|
||||
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).
|
||||
|
||||
**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) |
|
||||
[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)
|
||||
|
||||
[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest)
|
||||
[![CA Image](https://images.microbadger.com/badges/image/smallstep/step-ca.svg)](https://microbadger.com/images/smallstep/step-ca)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates)
|
||||
[![Build Status](https://travis-ci.com/smallstep/certificates.svg?branch=master)](https://travis-ci.com/smallstep/certificates)
|
||||
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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 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
|
||||
- [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)
|
||||
- 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
|
||||
|
||||
|
|
|
@ -10,11 +10,13 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -114,6 +116,17 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb
|
|||
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 {
|
||||
config := &tls.Config{
|
||||
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)
|
||||
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,
|
||||
"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.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.Equals(t, updch.Error.Type, err.Type)
|
||||
|
|
|
@ -124,10 +124,8 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...st
|
|||
ordersByAccountMux.Lock()
|
||||
defer ordersByAccountMux.Unlock()
|
||||
|
||||
var oldOids []string
|
||||
b, err := db.db.Get(ordersByAccountIDTable, []byte(accID))
|
||||
var (
|
||||
oldOids []string
|
||||
)
|
||||
if err != nil {
|
||||
if !nosql.IsErrNotFound(err) {
|
||||
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/db"
|
||||
"github.com/smallstep/nosql"
|
||||
"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),
|
||||
}
|
||||
},
|
||||
"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 {
|
||||
oldOids := []string{"foo", "bar"}
|
||||
bOldOids, err := json.Marshal(oldOids)
|
||||
|
|
|
@ -400,7 +400,7 @@ func logOtt(w http.ResponseWriter, token string) {
|
|||
func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
||||
if rl, ok := w.(logging.ResponseLogger); ok {
|
||||
m := map[string]interface{}{
|
||||
"serial": cert.SerialNumber,
|
||||
"serial": cert.SerialNumber.String(),
|
||||
"subject": cert.Subject.CommonName,
|
||||
"issuer": cert.Issuer.CommonName,
|
||||
"valid-from": cert.NotBefore.Format(time.RFC3339),
|
||||
|
@ -418,7 +418,7 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
|||
if len(val.CredentialID) > 0 {
|
||||
m["provisioner"] = fmt.Sprintf("%s (%s)", val.Name, val.CredentialID)
|
||||
} else {
|
||||
m["provisioner"] = val.Name
|
||||
m["provisioner"] = string(val.Name)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
@ -174,6 +174,9 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
|
|||
}
|
||||
|
||||
// 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 {
|
||||
if reuseKey, err := prov.GetTokenID(token); err == nil {
|
||||
if reuseKey == "" {
|
||||
|
|
|
@ -131,9 +131,10 @@ func (p *Azure) GetTokenID(token string) (string, error) {
|
|||
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 {
|
||||
return claims.ID, nil
|
||||
return "", ErrAllowTokenReuse
|
||||
}
|
||||
|
||||
sum := sha256.Sum256([]byte(claims.XMSMirID))
|
||||
|
|
|
@ -72,7 +72,7 @@ func TestAzure_GetTokenID(t *testing.T) {
|
|||
wantErr bool
|
||||
}{
|
||||
{"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 claims", p1, args{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey.fooo"}, "", true},
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -32,6 +33,17 @@ type Interface interface {
|
|||
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.
|
||||
type Audiences struct {
|
||||
Sign []string
|
||||
|
|
|
@ -383,7 +383,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
|
|||
}
|
||||
rci.ProvisionerID = p.GetID()
|
||||
rci.TokenID, err = p.GetTokenID(revokeOpts.OTT)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, provisioner.ErrAllowTokenReuse) {
|
||||
return errs.Wrap(http.StatusInternalServerError, err,
|
||||
"authority.Revoke; could not get ID for token")
|
||||
}
|
||||
|
|
|
@ -24,4 +24,7 @@ VOLUME ["/home/step"]
|
|||
STOPSIGNAL SIGTERM
|
||||
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
|
||||
|
|
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
|
||||
|
||||
* [Building From Source](#building-from-source)
|
||||
* [Asking Support Questions](#asking-support-questions)
|
||||
* [Reporting Issues](#reporting-issues)
|
||||
* [Submitting Patches](#submitting-patches)
|
||||
* [Code Contribution Guidelines](#code-contribution-guidelines)
|
||||
* [Git Commit Message Guidelines](#git-commit-message-guidelines)
|
||||
- [Contributing to `step certificates`](#contributing-to-step-certificates)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Building From Source](#building-from-source)
|
||||
- [Build a standard `step-ca`](#build-a-standard-step-ca)
|
||||
- [Build `step-ca` using CGO](#build-step-ca-using-cgo)
|
||||
- [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
|
||||
|
||||
|
@ -73,7 +81,7 @@ When the build is complete, you will find binaries in `bin/`.
|
|||
|
||||
## 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
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Provisioners
|
||||
|
||||
> 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.
|
||||
|
||||
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
|
||||
what you think of our tools. Get in touch over
|
||||
[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
|
||||
|
||||
|
|
3
go.mod
3
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/ThalesIgnite/crypto11 v1.2.4
|
||||
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-kit/kit v0.10.0 // indirect
|
||||
github.com/go-piv/piv-go v1.7.0
|
||||
|
@ -22,7 +23,7 @@ require (
|
|||
github.com/rs/xid v1.2.1
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
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/urfave/cli v1.22.4
|
||||
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/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/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658 h1:/WBjuutuivOA02gpDtrvrWKw01ugkyt3QnimB7enbtI=
|
||||
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 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
|
||||
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.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/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA=
|
||||
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/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.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/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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/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/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.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
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-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/nosql v0.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po=
|
||||
github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ=
|
||||
github.com/smallstep/nosql v0.3.8 h1:1/EWUbbEdz9ai0g9Fd09VekVjtxp+5+gIHpV2PdwW3o=
|
||||
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/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
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,
|
||||
},
|
||||
apiv1.SHA512WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512,
|
||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512,
|
||||
},
|
||||
apiv1.SHA256WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
|
||||
|
|
|
@ -59,7 +59,9 @@ func Parse(rawuri string) (*URI, error) {
|
|||
if u.Scheme == "" {
|
||||
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 {
|
||||
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 \
|
||||
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.
|
||||
; (In systemd 243 and above, you can use ExecCondition= here.)
|
||||
ExecStartPre=/usr/bin/env bash -c \
|
||||
'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'
|
||||
; (In systemd 242 or below, you can use ExecStartPre= here.)
|
||||
ExecCondition=/usr/bin/step certificate needs-renewal $CERT_LOCATION
|
||||
|
||||
; ExecStart renews the certificate, if ExecStartPre was successful.
|
||||
ExecStart=/usr/bin/step ca renew --force $CERT_LOCATION $KEY_LOCATION
|
||||
|
|
Loading…
Reference in a new issue