Merge branch 'master' into linkedca

This commit is contained in:
Mariano Cano 2021-08-25 15:56:50 -07:00 committed by GitHub
commit 42fde8ba28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 490 additions and 47 deletions

9
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View 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!

View file

@ -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

View file

@ -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

View file

@ -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)
[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest) [![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) [![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) [![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) [![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 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

View file

@ -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))
} }

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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
} }

View file

@ -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 == "" {

View file

@ -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))

View file

@ -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},
} }

View file

@ -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

View file

@ -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")
} }

View file

@ -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
View 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 "${@}"

View file

@ -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

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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,

View file

@ -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)
} }

View file

@ -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
View 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
View 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"

View file

@ -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