Skip to content

Commit

Permalink
Do not require schema in ca-url
Browse files Browse the repository at this point in the history
  • Loading branch information
maraino committed Nov 27, 2018
1 parent 9f4ecf1 commit 89ace4c
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 17 deletions.
11 changes: 4 additions & 7 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Expand Up @@ -62,3 +62,7 @@ required = [
[prune]
go-tests = true
unused-packages = true

[[constraint]]
branch = "master"
name = "github.com/smallstep/certificates"
8 changes: 8 additions & 0 deletions command/ca/bootstrap.go
Expand Up @@ -73,11 +73,19 @@ func bootstrapAction(ctx *cli.Context) error {
return errs.FileError(err, configFile)
}

// Serialize root
_, err = pemutil.Serialize(resp.RootPEM.Certificate, pemutil.ToFile(rootFile, 0600))
if err != nil {
return err
}

// make sure to store the url with https
caURL, err = completeURL(caURL)
if err != nil {
return err
}

// Serialize defaults.json
b, err := json.MarshalIndent(bootstrapConfig{
CA: caURL,
Fingerprint: fingerprint,
Expand Down
42 changes: 42 additions & 0 deletions command/ca/ca.go
@@ -1,6 +1,10 @@
package ca

import (
"net/url"
"strings"

"github.com/pkg/errors"
"github.com/smallstep/cli/command"
"github.com/smallstep/cli/command/ca/provisioner"
"github.com/urfave/cli"
Expand Down Expand Up @@ -132,3 +136,41 @@ unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns",
generating key.`,
}
)

// completeURL parses and validates the given URL. It supports general
// URLs like https://ca.smallstep.com[:port][/path], and incomplete URLs like
// ca.smallstep.com[:port][/path].
func completeURL(rawurl string) (string, error) {
u, err := url.Parse(rawurl)
if err != nil {
return "", errors.Wrapf(err, "error parsing url '%s'", rawurl)
}

// URLs are generally parsed as:
// [scheme:][//[userinfo@]host][/]path[?query][#fragment]
// But URLs that do not start with a slash after the scheme are interpreted as
// scheme:opaque[?query][#fragment]
if u.Opaque == "" {
if u.Scheme == "" {
u.Scheme = "https"
}
if u.Host == "" {
// rawurl looks like ca.smallstep.com or ca.smallstep.com/1.0/sign
if u.Path != "" {
parts := strings.SplitN(u.Path, "/", 2)
u.Host = parts[0]
if len(parts) == 2 {
u.Path = parts[1]
} else {
u.Path = ""
}
return completeURL(u.String())
}
return "", errors.Errorf("error parsing url '%s'", rawurl)
}
return u.String(), nil
}
// scheme:opaque[?query][#fragment]
// rawurl looks like ca.smallstep.com:443 or ca.smallstep.com:443/1.0/sign
return completeURL("https://" + rawurl)
}
42 changes: 32 additions & 10 deletions command/ca/token.go
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"os"
"strings"
"time"

"github.com/pkg/errors"
Expand Down Expand Up @@ -168,11 +169,11 @@ func newTokenAction(ctx *cli.Context) error {
return errs.RequiredWithFlag(ctx, "offline", "key")
}

audience, err := url.Parse(caURL)
if err != nil || audience.Scheme != "https" {
return errs.InvalidFlagValue(ctx, "ca-url", caURL, "")
// Get audience from ca-url
audience, err := parseAudience(ctx)
if err != nil {
return err
}
audience = audience.ResolveReference(&url.URL{Path: "/1.0/sign"})

var opts []jose.Option
if len(passwordFile) != 0 {
Expand All @@ -183,7 +184,7 @@ func newTokenAction(ctx *cli.Context) error {
return err
}

token, err = generateToken(subject, kid, issuer, audience.String(), root, notBefore, notAfter, jwk)
token, err = generateToken(subject, kid, issuer, audience, root, notBefore, notAfter, jwk)
if err != nil {
return err
}
Expand All @@ -200,6 +201,27 @@ func newTokenAction(ctx *cli.Context) error {
return nil
}

// parseAudience creates the ca audience url from the ca-url
func parseAudience(ctx *cli.Context) (string, error) {
caURL := ctx.String("ca-url")
if len(caURL) == 0 {
return "", errs.RequiredFlag(ctx, "ca-url")
}

audience, err := url.Parse(caURL)
if err != nil {
return "", errs.InvalidFlagValue(ctx, "ca-url", caURL, "")
}
switch strings.ToLower(audience.Scheme) {
case "https", "":
audience.Scheme = "https"
audience = audience.ResolveReference(&url.URL{Path: "/1.0/sign"})
return audience.String(), nil
default:
return "", errs.InvalidFlagValue(ctx, "ca-url", caURL, "")
}
}

// generateToken generates a provisioning or bootstrap token with the given
// parameters.
func generateToken(sub, kid, iss, aud, root string, notBefore, notAfter time.Time, jwk *jose.JSONWebKey) (string, error) {
Expand Down Expand Up @@ -238,11 +260,11 @@ func generateToken(sub, kid, iss, aud, root string, notBefore, notAfter time.Tim

// newTokenFlow implements the common flow used to generate a token
func newTokenFlow(ctx *cli.Context, subject, caURL, root, kid, issuer, passwordFile, keyFile string, notBefore, notAfter time.Time) (string, error) {
audience, err := url.Parse(caURL)
if err != nil || audience.Scheme != "https" {
return "", errs.InvalidFlagValue(ctx, "ca-url", caURL, "")
// Get audience from ca-url
audience, err := parseAudience(ctx)
if err != nil {
return "", err
}
audience = audience.ResolveReference(&url.URL{Path: "/1.0/sign"})

provisioners, err := pki.GetProvisioners(caURL, root)
if err != nil {
Expand Down Expand Up @@ -327,7 +349,7 @@ func newTokenFlow(ctx *cli.Context, subject, caURL, root, kid, issuer, passwordF
}
}

return generateToken(subject, kid, issuer, audience.String(), root, notBefore, notAfter, jwk)
return generateToken(subject, kid, issuer, audience, root, notBefore, notAfter, jwk)
}

func parseTimeOrDuration(s string) (time.Time, bool) {
Expand Down

0 comments on commit 89ace4c

Please sign in to comment.