forked from TrueCloudLab/certificates
Add new example and docs on the client SDK.
This commit is contained in:
parent
7eb8aeb1f1
commit
6617c93732
2 changed files with 287 additions and 4 deletions
|
@ -1,6 +1,137 @@
|
|||
# Example
|
||||
# Examples
|
||||
|
||||
# Bootstrap Client & Server
|
||||
## 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.GetClientTLSConfig(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
|
||||
|
|
152
examples/basic-client/client.go
Normal file
152
examples/basic-client/client.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
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)
|
||||
// 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)
|
||||
// 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)
|
||||
}
|
Loading…
Reference in a new issue