Merge pull request #4 from smallstep/sdk-improvements

SDK improvements
This commit is contained in:
Mariano Cano 2018-11-06 15:17:44 -08:00 committed by GitHub
commit 4518be9fd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 888 additions and 12 deletions

114
ca/bootstrap.go Normal file
View file

@ -0,0 +1,114 @@
package ca
import (
"context"
"net/http"
"strings"
"github.com/pkg/errors"
"github.com/smallstep/cli/jose"
"gopkg.in/square/go-jose.v2/jwt"
)
type tokenClaims struct {
SHA string `json:"sha"`
jose.Claims
}
// Bootstrap is a helper function that initializes a client with the
// configuration in the bootstrap token.
func Bootstrap(token string) (*Client, error) {
tok, err := jwt.ParseSigned(token)
if err != nil {
return nil, errors.Wrap(err, "error parsing token")
}
var claims tokenClaims
if err := tok.UnsafeClaimsWithoutVerification(&claims); err != nil {
return nil, errors.Wrap(err, "error parsing token")
}
// Validate bootstrap token
switch {
case len(claims.SHA) == 0:
return nil, errors.New("invalid bootstrap token: sha claim is not present")
case !strings.HasPrefix(strings.ToLower(claims.Audience[0]), "http"):
return nil, errors.New("invalid bootstrap token: aud claim is not a url")
}
return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA))
}
// BootstrapServer is a helper function that returns an http.Server configured
// with the given address and handler, and prepared to use TLS connections. The
// certificate will automatically rotate if necessary.
//
// Usage:
// srv, err := ca.BootstrapServer(":443", token, handler)
// if err != nil {
// return err
// }
// srv.ListenAndServeTLS("", "")
func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, error) {
client, err := Bootstrap(token)
if err != nil {
return nil, err
}
req, pk, err := CreateSignRequest(token)
if err != nil {
return nil, err
}
sign, err := client.Sign(req)
if err != nil {
return nil, err
}
tlsConfig, err := client.GetServerTLSConfig(context.Background(), sign, pk)
if err != nil {
return nil, err
}
return &http.Server{
Addr: addr,
Handler: handler,
TLSConfig: tlsConfig,
}, nil
}
// BootstrapClient is a helper function that using the given bootstrap token
// return an http.Client configured with a Transport prepared to do TLS
// connections using the client certificate returned by the certificate
// authority. The certificate will automatically rotate if necessary.
//
// Usage:
// client, err := ca.BootstrapClient(token)
// if err != nil {
// return err
// }
// resp, err := client.Get("https://internal.smallstep.com")
func BootstrapClient(token string) (*http.Client, error) {
client, err := Bootstrap(token)
if err != nil {
return nil, err
}
req, pk, err := CreateSignRequest(token)
if err != nil {
return nil, err
}
sign, err := client.Sign(req)
if err != nil {
return nil, err
}
transport, err := client.Transport(context.Background(), sign, pk)
if err != nil {
return nil, err
}
return &http.Client{
Transport: transport,
}, nil
}

219
ca/bootstrap_test.go Normal file
View file

@ -0,0 +1,219 @@
package ca
import (
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/cli/crypto/randutil"
stepJOSE "github.com/smallstep/cli/jose"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
func startCABootstrapServer() *httptest.Server {
config, err := authority.LoadConfiguration("testdata/ca.json")
if err != nil {
panic(err)
}
srv := httptest.NewUnstartedServer(nil)
config.Address = srv.Listener.Addr().String()
ca, err := New(config)
if err != nil {
panic(err)
}
srv.Config.Handler = ca.srv.Handler
srv.TLS = ca.srv.TLSConfig
srv.StartTLS()
// Force the use of GetCertificate on IPs
srv.TLS.Certificates = nil
return srv
}
func generateBootstrapToken(ca, subject, sha string) string {
now := time.Now()
jwk, err := stepJOSE.ParseKey("testdata/secrets/ott_mariano_priv.jwk", stepJOSE.WithPassword([]byte("password")))
if err != nil {
panic(err)
}
opts := new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, opts)
if err != nil {
panic(err)
}
id, err := randutil.ASCII(64)
if err != nil {
panic(err)
}
cl := struct {
SHA string `json:"sha"`
jwt.Claims
}{
SHA: sha,
Claims: jwt.Claims{
ID: id,
Subject: subject,
Issuer: "mariano",
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
Audience: []string{ca + "/sign"},
},
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
if err != nil {
panic(err)
}
return raw
}
func TestBootstrap(t *testing.T) {
srv := startCABootstrapServer()
defer srv.Close()
token := generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
client, err := NewClient(srv.URL+"/sign", WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil {
t.Fatal(err)
}
type args struct {
token string
}
tests := []struct {
name string
args args
want *Client
wantErr bool
}{
{"ok", args{token}, client, false},
{"token err", args{"badtoken"}, nil, true},
{"bad claims", args{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.foo.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}, nil, true},
{"bad sha", args{generateBootstrapToken(srv.URL, "subject", "")}, nil, true},
{"bad aud", args{generateBootstrapToken("", "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Bootstrap(tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("Bootstrap() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Bootstrap() = %v, want %v", got, tt.want)
}
} else {
if got == nil {
t.Error("Bootstrap() = nil, want not nil")
} else {
if !reflect.DeepEqual(got.endpoint, tt.want.endpoint) {
t.Errorf("Bootstrap() endpoint = %v, want %v", got.endpoint, tt.want.endpoint)
}
if !reflect.DeepEqual(got.certPool, tt.want.certPool) {
t.Errorf("Bootstrap() certPool = %v, want %v", got.certPool, tt.want.certPool)
}
}
}
})
}
}
func TestBootstrapServer(t *testing.T) {
srv := startCABootstrapServer()
defer srv.Close()
token := func() string {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
type args struct {
addr string
token string
handler http.Handler
}
tests := []struct {
name string
args args
wantErr bool
}{
{"ok", args{":0", token(), handler}, false},
{"fail", args{":0", "bad-token", handler}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := BootstrapServer(tt.args.addr, tt.args.token, tt.args.handler)
if (err != nil) != tt.wantErr {
t.Errorf("BootstrapServer() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
if got != nil {
t.Errorf("BootstrapServer() = %v, want nil", got)
}
} else {
if !reflect.DeepEqual(got.Addr, tt.args.addr) {
t.Errorf("BootstrapServer() Addr = %v, want %v", got.Addr, tt.args.addr)
}
if got.TLSConfig == nil || got.TLSConfig.ClientCAs == nil || got.TLSConfig.RootCAs == nil || got.TLSConfig.GetCertificate == nil || got.TLSConfig.GetClientCertificate == nil {
t.Errorf("BootstrapServer() invalid TLSConfig = %#v", got.TLSConfig)
}
}
})
}
}
func TestBootstrapClient(t *testing.T) {
srv := startCABootstrapServer()
defer srv.Close()
token := func() string {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
type args struct {
token string
}
tests := []struct {
name string
args args
wantErr bool
}{
{"ok", args{token()}, false},
{"fail", args{"bad-token"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := BootstrapClient(tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("BootstrapClient() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
if got != nil {
t.Errorf("BootstrapClient() = %v, want nil", got)
}
} else {
tlsConfig := got.Transport.(*http.Transport).TLSClientConfig
if tlsConfig == nil || tlsConfig.ClientCAs != nil || tlsConfig.GetClientCertificate == nil || tlsConfig.RootCAs == nil || tlsConfig.GetCertificate != nil {
t.Errorf("BootstrapClient() invalid Transport = %#v", tlsConfig)
}
resp, err := got.Post(srv.URL+"/renew", "application/json", http.NoBody)
if err != nil {
t.Errorf("BootstrapClient() failed renewing certificate")
return
}
var renewal api.SignResponse
if err := readJSON(resp.Body, &renewal); err != nil {
t.Errorf("BootstrapClient() error reading response: %v", err)
return
}
if renewal.CaPEM.Certificate == nil || renewal.ServerPEM.Certificate == nil {
t.Errorf("BootstrapClient() invalid renewal response: %v", renewal)
}
}
})
}
}

227
examples/README.md Normal file
View file

@ -0,0 +1,227 @@
# Examples
## Basic client usage
The basic-client example shows the use of the most of the functioanlity of the
`ca.Client`, those methods works as an SDK for integrating other services with
the Certificate Authority (CA).
In [basic-client/client.go](/examples/basic-client/client.go) we first can see
the initialization of the client:
```go
client, err := ca.NewClient("https://localhost:9000", ca.WithRootSHA256("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d"))
```
The previous code uses the CA address and the root certificate fingerprint.
The CA url will be present in the token, and the root fingerprint can be present
too if the `--root root_ca.crt` option is use in the creation of the token. If
this is the case is simpler to rely in the token and use just:
```go
client, err := ca.Bootstrap(token)
```
After the initialization there're examples of all the client methods, they are
just a convenient way to use the CA API endpoints. The first method `Health`
returns the status of the CA server, on the first implementation if the server
is up it will return just ok.
```go
health, err := client.Health()
// Health is a struct created from the JSON response {"status": "ok"}
```
The next method `Root` is used to get and verify the root certificate. We will
pass a finger print, it will download the root certificate from the CA and it
will make sure that the fingerprint matches. This method uses an insecure HTTP
client as it might be used in the initialization of the client, but the response
is considered secure because we have compared against the given digest.
```go
root, err := client.Root("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d")
```
After Root we have the most important method `Sign`, this is used to sign a
Certificate Signing Request that we provide. To secure this request we use
the one-time-token. You can build your own certificate request and add it in
the `*api.SignRequest`, but the ca package contains a method that will create a
secure random key, and create the CSR based on the information in the token.
```go
// Create a CSR from token and return the sign request, the private key and an
// error if something failed.
req, pk, err := ca.CreateSignRequest(token)
if err != nil { ... }
// Do the sign request and return the signed certificate
sign, err := client.Sign(req)
if err != nil { ... }
```
To renew the certificate we can use the `Renew` method, the certificate renewal
relies on a mTLS connection with a previous certificate, so we will need to pass
a transport with the previous certificate.
```go
// Get a cancelable context to stop the renewal goroutines and timers.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create a transport from with the sign response and the private key.
tr, err := client.Transport(ctx, sign, pk)
if err != nil { ... }
// Renew the certificate and get the new ones.
// The return type are equivalent to ones in the Sign method.
renew, err := client.Renew(tr)
if err != nil { ... }
```
All the previous methods map with one endpoint in the CA API, but the API
provides a couple more that are used for creating the tokens. For those we have
a couple of methods, one that returns a list of provisioners and one that
returns the encrypted key of one provisioner.
```go
// Without options it will return the first 20 provisioners
provisioners, err := client.Provisioners()
// We can also set a limit up to 100
provisioners, err := client.Provisioners(ca.WithProvisionerLimit(100))
// With a pagination cursor
provisioners, err := client.Provisioners(ca.WithProvisionerCursor("1f18c1ecffe54770e9107ce7b39b39735"))
// Or combining both
provisioners, err := client.Provisioners(
ca.WithProvisionerCursor("1f18c1ecffe54770e9107ce7b39b39735"),
ca.WithProvisionerLimit(100),
)
// Return the encrypted key of one of the returned provisioners. The key
// returned is an encrypted JWE with the private key used to sign tokens.
key, err := client.ProvisionerKey("DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk")
```
The example shows also the use of some helper methods used to get configured
tls.Config objects that can be injected in servers and clients. These methods,
are also configured to auto-renew the certificate once two thirds of the
duration of the certificate has passed, approximately.
```go
// Get a cancelable context to stop the renewal goroutines and timers.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Get tls.Config for a server
tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk)
// Get tls.Config for a client
tlsConfig, err := client.GetClientTLSConfig(ctx, sign, pk)
// Get an http.Transport for a client, this can be used as a http.RoundTripper
// in an http.Client
tr, err := client.Transport(ctx, sign, pk)
```
To run the example you need to start the certificate authority:
```
certificates $ bin/step-ca examples/pki/config/ca.json
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
```
And just run the client.go with a new token:
```
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/basic-client/client.go $(step ca new-token client.smallstep.com))
```
## Bootstrap Client & Server
On this example we are going to see the Certificate Authority running, as well
as a simple Server using TLS and a simple client doing TLS requests to the
server.
The examples directory already contains a sample pki configuration with the
password `password` hardcoded, but you can create your own using `step ca init`.
First we will start the certificate authority:
```
certificates $ bin/step-ca examples/pki/config/ca.json
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
```
We will start the server and we will type `password` when step asks for the
provisioner password:
```
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost))
✔ Key ID: DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk (mariano@smallstep.com)
Please enter the password to decrypt the provisioner key:
Listening on :8443 ...
```
We try that using cURL with the system certificates it will return an error:
```
certificates $ curl https://localhost:8443
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html
curl performs SSL certificate verification by default, using a "bundle"
of Certificate Authority (CA) public keys (CA certs). If the default
bundle file isn't adequate, you can specify an alternate file
using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
the bundle, the certificate verification probably failed due to a
problem with the certificate (it might be expired, or the name might
not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
the -k (or --insecure) option.
HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.
```
But if we use the root certificate it will properly work:
```
certificates $ curl --cacert examples/pki/secrets/root_ca.crt https://localhost:8443
Hello nobody at 2018-11-03 01:49:25.66912 +0000 UTC!!!
```
Notice that in the response we see `nobody`, this is because the server didn't
detected a TLS client configuration.
But if we the client with the certificate name Mike we'll see:
```
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-client/client.go $(step ca new-token Mike)
✔ Key ID: DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk (mariano@smallstep.com)
Please enter the password to decrypt the provisioner key:
Server responded: Hello Mike at 2018-11-03 01:52:52.678215 +0000 UTC!!!
Server responded: Hello Mike at 2018-11-03 01:52:53.681563 +0000 UTC!!!
Server responded: Hello Mike at 2018-11-03 01:52:54.682787 +0000 UTC!!!
...
```
## Certificate rotation
We can use the bootstrap-server to demonstrate the certificate rotation. We've
added second provisioner to to the ca with the name of `mike@smallstep.com`,
this provisioner is configured with a default certificate duration of 2 minutes.
If we run the server, and inspect the used certificate, we can verify how it
rotates after approximately two thirds of the duration has passed.
```
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost))
✔ Key ID: YYNxZ0rq0WsT2MlqLCWvgme3jszkmt99KjoGEJJwAKs (mike@smallstep.com)
Please enter the password to decrypt the provisioner key:
Listening on :8443 ...
```
In this specific case, the the rotation will happen after 74-80 seconds have
passed, the exact formula is 120-120/3-rand(120/20), where rand will return a
number between 0 and 6.
We can use the following command to check the certificate expiration and to make
sure the certificate changes after 74-80 seconds.
```
certificates $ step certificate inspect --insecure https://localhost:8443
```

View file

@ -0,0 +1,158 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/smallstep/certificates/ca"
)
func printResponse(name string, v interface{}) {
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("%s response:\n%s\n\n", name, b)
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s <token>\n", os.Args[0])
os.Exit(1)
}
token := os.Args[1]
// To create the client using ca.NewClient we need:
// * The CA address "https://localhost:9000"
// * The root certificate fingerprint
// 84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d to get
// the root fingerprint we can use `step certificate fingerprint root_ca.crt`
client, err := ca.NewClient("https://localhost:9000", ca.WithRootSHA256("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d"))
if err != nil {
panic(err)
}
// Other ways to initialize the client would be:
// * With the Bootstrap functionality (recommended):
// client, err := ca.Bootstrap(token)
// * Using the root certificate instead of the fingerprint:
// client, err := ca.NewClient("https://localhost:9000", ca.WithRootFile("../pki/secrets/root_ca.crt"))
// Get the health of the CA
health, err := client.Health()
if err != nil {
panic(err)
}
printResponse("Health", health)
// Get and verify a root CA
root, err := client.Root("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d")
if err != nil {
panic(err)
}
printResponse("Root", root)
// We can use ca.CreateSignRequest to generate a new sign request with a
// randomly generated key.
req, pk, err := ca.CreateSignRequest(token)
if err != nil {
panic(err)
}
sign, err := client.Sign(req)
if err != nil {
panic(err)
}
printResponse("Sign", sign)
// Renew a certificate with a transport that contains the previous
// certificate. We should created a context that allows us to finish the
// renewal goroutine.∑
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Finish the renewal goroutine
tr, err := client.Transport(ctx, sign, pk)
if err != nil {
panic(err)
}
renew, err := client.Renew(tr)
if err != nil {
panic(err)
}
printResponse("Renew", renew)
// Get tls.Config for a server
ctxServer, cancelServer := context.WithCancel(context.Background())
defer cancelServer()
tlsConfig, err := client.GetServerTLSConfig(ctxServer, sign, pk)
if err != nil {
panic(err)
}
// An http server will use the tls.Config like:
_ = &http.Server{
Addr: ":443",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}),
TLSConfig: tlsConfig,
}
// Get tls.Config for a client
ctxClient, cancelClient := context.WithCancel(context.Background())
defer cancelClient()
tlsConfig, err = client.GetClientTLSConfig(ctxClient, sign, pk)
if err != nil {
panic(err)
}
// An http.Client will need to create a transport first
_ = &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
// Options set in http.DefaultTransport
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
// But we can just use client.Transport to get the default configuration
ctxTransport, cancelTransport := context.WithCancel(context.Background())
defer cancelTransport()
tr, err = client.Transport(ctxTransport, sign, pk)
if err != nil {
panic(err)
}
// And http.Client will use the transport like
_ = &http.Client{
Transport: tr,
}
// Get provisioners and provisioner keys. In this example we add two
// optional arguments with the initial cursor and a limit.
//
// A server or a client should not need this functionality, they are used to
// sign (private key) and verify (public key) tokens. The step cli can be
// used for this purpose.
provisioners, err := client.Provisioners(ca.WithProvisionerCursor(""), ca.WithProvisionerLimit(100))
if err != nil {
panic(err)
}
printResponse("Provisioners", provisioners)
// Get encrypted key
key, err := client.ProvisionerKey("DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk")
if err != nil {
panic(err)
}
printResponse("Provisioner Key", key)
}

View file

@ -0,0 +1,39 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"time"
"github.com/smallstep/certificates/ca"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s <token>\n", os.Args[0])
os.Exit(1)
}
token := os.Args[1]
client, err := ca.BootstrapClient(token)
if err != nil {
panic(err)
}
for {
resp, err := client.Get("https://localhost:8443")
if err != nil {
panic(err)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
panic(err)
}
fmt.Printf("Server responded: %s\n", b)
time.Sleep(1 * time.Second)
}
}

View file

@ -0,0 +1,35 @@
package main
import (
"fmt"
"net/http"
"os"
"time"
"github.com/smallstep/certificates/ca"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s <token>\n", os.Args[0])
os.Exit(1)
}
token := os.Args[1]
srv, err := ca.BootstrapServer(":8443", token, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := "nobody"
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
name = r.TLS.PeerCertificates[0].Subject.CommonName
}
w.Write([]byte(fmt.Sprintf("Hello %s at %s!!!", name, time.Now().UTC())))
}))
if err != nil {
panic(err)
}
fmt.Println("Listening on :8443 ...")
if err := srv.ListenAndServeTLS("", ""); err != nil {
panic(err)
}
}

View file

@ -1,6 +0,0 @@
{
"caPath": "/path/to/intermediate-certificate",
"caPrivateKeyPath": "/path/to/intermediate-private-key",
"caPasscode": "very-secure-passcode",
"listenAddress": "127.0.0.1:9000"
}

View file

@ -1,6 +0,0 @@
country: USA
locality: San Francisco
organization: smallstep
common_name: internal.smallstep.com
key_type: rsa
rsa_bits: 4096

View file

@ -0,0 +1,58 @@
{
"root": "examples/pki/secrets/root_ca.crt",
"crt": "examples/pki/secrets/intermediate_ca.crt",
"key": "examples/pki/secrets/intermediate_ca_key",
"password": "password",
"address": ":9000",
"dnsNames": [
"localhost"
],
"logger": {
"format": "text"
},
"authority": {
"provisioners": [
{
"name": "mariano@smallstep.com",
"type": "jwk",
"key": {
"use": "sig",
"kty": "EC",
"kid": "DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk",
"crv": "P-256",
"alg": "ES256",
"x": "jXoO1j4CXxoTC32pNzkVC8l6k2LfP0k5ndhJZmcdVbk",
"y": "c3JDL4GTFxJWHa8EaHdMh4QgwMh64P2_AGWrD0ADXcI"
},
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiOTFVWjdzRGw3RlNXcldfX1I1NUh3USJ9.FcWtrBDNgrkA33G9Ll9sXh1cPF-3jVXeYe1FLmSDc_Q2PmfLOPvJOA.0ZoN32ayaRWnufJb.WrkffMmDLWiq1-2kn-w7-kVBGW12gjNCBHNHB1hyEdED0rWH1YWpKd8FjoOACdJyLhSn4kAS3Lw5AH7fvO27A48zzvoxZU5EgSm5HG9IjkIH-LBJ-v79ShkpmPylchgjkFhxa5epD11OIK4rFmI7s-0BCjmJokLR_DZBhDMw2khGnsr_MEOfAz9UnqXaQ4MIy8eT52xUpx68gpWFlz2YP3EqiYyNEv0PpjMtyP5lO2i8-p8BqvuJdus9H3fO5Dg-1KVto1wuqh4BQ2JKTauv60QAnM_4sdxRHku3F_nV64SCrZfDvnN2ve21raFROtyXaqHZhN6lyoPxDncy8v4.biaOblEe0N-gMpJyFZ-3-A"
},
{
"name": "mike@smallstep.com",
"type": "jwk",
"key": {
"use": "sig",
"kty": "EC",
"kid": "YYNxZ0rq0WsT2MlqLCWvgme3jszkmt99KjoGEJJwAKs",
"crv": "P-256",
"alg": "ES256",
"x": "LsI8nHBflc-mrCbRqhl8d3hSl5sYuSM1AbXBmRfznyg",
"y": "F99LoOvi7z-ZkumsgoHIhodP8q9brXe4bhF3szK-c_w"
},
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiVERQS2dzcEItTUR4ZDJxTGo0VlpwdyJ9.2_j0cZgTm2eFkZ-hrtr1hBIvLxN0w3TZhbX0Jrrq7vBMaywhgFcGTA.mCasZCbZJ-JT7vjA.bW052WDKSf_ueEXq1dyxLq0n3qXWRO-LXr7OzBLdUKWKSBGQrzqS5KJWqdUCPoMIHTqpwYvm-iD6uFlcxKBYxnsAG_hoq_V3icvvwNQQSd_q7Thxr2_KtPIDJWNuX1t5qXp11hkgb-8d5HO93CmN7xNDG89pzSUepT6RYXOZ483mP5fre9qzkfnrjx3oPROCnf3SnIVUvqk7fwfXuniNsg3NrNqncHYUQNReiq3e9I1R60w0ZQTvIReY7-zfiq7iPgVqmu5I7XGgFK4iBv0L7UOEora65b4hRWeLxg5t7OCfUqrS9yxAk8FdjFb9sEfjopWViPRepB0dYPH8dVI.fb6-7XWqp0j6CR9Li0NI-Q",
"claims": {
"minTLSCertDuration": "60s",
"defaultTLSCertDuration": "120s"
}
}
]
},
"tls": {
"cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
],
"minVersion": 1.2,
"maxVersion": 1.2,
"renegotiation": false
}
}

View file

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBxjCCAWugAwIBAgIQAYoOWhdChUmmKzlc0DWcWDAKBggqhkjOPQQDAjAcMRow
GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xODExMDIyMzU0MTNaFw0yODEw
MzAyMzU0MTNaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASxvIWme8/yDAxkR63KgSYkpN7mHKBH
k5c8S+uzba4xWbaxZtEZ9NNhEIAgYFZ9/3ThrzLOsuGwRCvPTaD5iycQo4GGMIGD
MA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU8dKIy5ZLH2h3ihWgqjcpoo5e
q3YwHwYDVR0jBBgwFoAU0IpOvAyBnn9UhDqOQzXnfEU3aYMwCgYIKoZIzj0EAwID
SQAwRgIhANXlcktuaEvORhgRvzQ6vVNgvpqCEXW3CcCHjUl1xSdaAiEAmakkpfFq
VsT5PqPnTRgOWlFESRhQ9btl6nQ+2Lt/S5A=
-----END CERTIFICATE-----

View file

@ -0,0 +1,8 @@
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,4c7758e66df1884f6560839de64d4dd3
S8Ha8uA+bA3IGPurYODwd9VaJZ6FHI2tlznHXCOxT1MlGqyEAc4aWS11QBUz0Ucp
excwlqM8kfh5BcN5a+vvInHnv74ZiNPdpt/apzz2LIx52pApzASiKVXRsAUmR4Pv
3MsO1/cVHkilpee1uC+axL32d5YmyP0URpSNJK9BhZo=
-----END EC PRIVATE KEY-----

View file

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBfDCCASGgAwIBAgIQY0CXerxuM+EhTbpVxxLRKjAKBggqhkjOPQQDAjAcMRow
GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xODExMDIyMzU0MTNaFw0yODEw
MzAyMzU0MTNaMBwxGjAYBgNVBAMTEVNtYWxsc3RlcCBSb290IENBMFkwEwYHKoZI
zj0CAQYIKoZIzj0DAQcDQgAEEGa7ZeL4WVIfPFDS7glJkIVsITVQgjfyz+AhcYaS
rkJZlWOGZ60br9uE/wEfUcX1zavrX1Wz+bSJzTvT0AVBNqNFMEMwDgYDVR0PAQH/
BAQDAgGmMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFNCKTrwMgZ5/VIQ6
jkM153xFN2mDMAoGCCqGSM49BAMCA0kAMEYCIQCRA4EdlTTMhs2Zd1cT75ZgxeGa
mjVPl1vqBxLkHqEO+QIhAPKVm7E452ZBe2o5rQRxGwa94MI+CyuEIH9md3nTgWWX
-----END CERTIFICATE-----

View file

@ -0,0 +1,8 @@
-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,98fdc560ba714aebb9fd4b714395d8ce
2bFn8yRb8lMvDR6oh22PocfhXdaoVNt4QwHCJNy0K0fG8CMokwDfEec//LseP6rA
7/EV11+ZgoN9xyTNe1kB6zFv7/kzCpRm23sqtyio+8xXWnLZNYKBRYYEeJWBUqqd
GAfazg4ZFzoIH5TEPWCEAp7M9CVvtiw1SeA/zjewp2k=
-----END EC PRIVATE KEY-----