forked from TrueCloudLab/certificates
Add endpoint to return the SSH public keys.
Related to smallstep/ca-component#195
This commit is contained in:
parent
a197158426
commit
961be1fbc7
7 changed files with 232 additions and 53 deletions
|
@ -250,9 +250,12 @@ func (h *caHandler) Route(r Router) {
|
||||||
r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey)
|
r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey)
|
||||||
r.MethodFunc("GET", "/roots", h.Roots)
|
r.MethodFunc("GET", "/roots", h.Roots)
|
||||||
r.MethodFunc("GET", "/federation", h.Federation)
|
r.MethodFunc("GET", "/federation", h.Federation)
|
||||||
|
// SSH CA
|
||||||
|
r.MethodFunc("GET", "/ssh/sign", h.SignSSH)
|
||||||
|
r.MethodFunc("GET", "/ssh/keys", h.SSHKeys)
|
||||||
|
|
||||||
// For compatibility with old code:
|
// For compatibility with old code:
|
||||||
r.MethodFunc("POST", "/re-sign", h.Renew)
|
r.MethodFunc("POST", "/re-sign", h.Renew)
|
||||||
// SSH CA
|
|
||||||
r.MethodFunc("POST", "/sign-ssh", h.SignSSH)
|
r.MethodFunc("POST", "/sign-ssh", h.SignSSH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -512,6 +512,7 @@ type mockAuthority struct {
|
||||||
getEncryptedKey func(kid string) (string, error)
|
getEncryptedKey func(kid string) (string, error)
|
||||||
getRoots func() ([]*x509.Certificate, error)
|
getRoots func() ([]*x509.Certificate, error)
|
||||||
getFederation func() ([]*x509.Certificate, error)
|
getFederation func() ([]*x509.Certificate, error)
|
||||||
|
getSSHKeys func() (*authority.SSHKeys, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove once Authorize is deprecated.
|
// TODO: remove once Authorize is deprecated.
|
||||||
|
@ -617,6 +618,13 @@ func (m *mockAuthority) GetFederation() ([]*x509.Certificate, error) {
|
||||||
return m.ret1.([]*x509.Certificate), m.err
|
return m.ret1.([]*x509.Certificate), m.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockAuthority) GetSSHKeys() (*authority.SSHKeys, error) {
|
||||||
|
if m.getSSHKeys != nil {
|
||||||
|
return m.getSSHKeys()
|
||||||
|
}
|
||||||
|
return m.ret1.(*authority.SSHKeys), m.err
|
||||||
|
}
|
||||||
|
|
||||||
func Test_caHandler_Route(t *testing.T) {
|
func Test_caHandler_Route(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
Authority Authority
|
Authority Authority
|
||||||
|
|
100
api/ssh.go
100
api/ssh.go
|
@ -16,7 +16,7 @@ import (
|
||||||
type SSHAuthority interface {
|
type SSHAuthority interface {
|
||||||
SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
|
SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
|
||||||
SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
|
SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
|
||||||
SSHConfig() (*authority.SSHConfiguration, error)
|
GetSSHKeys() (*authority.SSHKeys, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignSSHRequest is the request body of an SSH certificate request.
|
// SignSSHRequest is the request body of an SSH certificate request.
|
||||||
|
@ -30,15 +30,29 @@ type SignSSHRequest struct {
|
||||||
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
|
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate validates the SignSSHRequest.
|
||||||
|
func (s *SignSSHRequest) Validate() error {
|
||||||
|
switch {
|
||||||
|
case s.CertType != "" && s.CertType != provisioner.SSHUserCert && s.CertType != provisioner.SSHHostCert:
|
||||||
|
return errors.Errorf("unknown certType %s", s.CertType)
|
||||||
|
case len(s.PublicKey) == 0:
|
||||||
|
return errors.New("missing or empty publicKey")
|
||||||
|
case len(s.OTT) == 0:
|
||||||
|
return errors.New("missing or empty ott")
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SignSSHResponse is the response object that returns the SSH certificate.
|
// SignSSHResponse is the response object that returns the SSH certificate.
|
||||||
type SignSSHResponse struct {
|
type SignSSHResponse struct {
|
||||||
Certificate SSHCertificate `json:"crt"`
|
Certificate SSHCertificate `json:"crt"`
|
||||||
AddUserCertificate *SSHCertificate `json:"addUserCrt,omitempty"`
|
AddUserCertificate *SSHCertificate `json:"addUserCrt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHConfigResponse represents the response object that returns the SSH user
|
// SSHKeysResponse represents the response object that returns the SSH user and
|
||||||
// and host keys.
|
// host keys.
|
||||||
type SSHConfigResponse struct {
|
type SSHKeysResponse struct {
|
||||||
UserKey *SSHPublicKey `json:"userKey,omitempty"`
|
UserKey *SSHPublicKey `json:"userKey,omitempty"`
|
||||||
HostKey *SSHPublicKey `json:"hostKey,omitempty"`
|
HostKey *SSHPublicKey `json:"hostKey,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -58,21 +72,6 @@ func (c SSHCertificate) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(`"` + s + `"`), nil
|
return []byte(`"` + s + `"`), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHPublicKey represents a public key in a response object.
|
|
||||||
type SSHPublicKey struct {
|
|
||||||
ssh.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface. Returns a quoted,
|
|
||||||
// base64 encoded, openssh wire format version of the public key.
|
|
||||||
func (p *SSHPublicKey) MarshalJSON() ([]byte, error) {
|
|
||||||
if p == nil || p.PublicKey == nil {
|
|
||||||
return []byte("null"), nil
|
|
||||||
}
|
|
||||||
s := base64.StdEncoding.EncodeToString(p.PublicKey.Marshal())
|
|
||||||
return []byte(`"` + s + `"`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaler interface. The certificate is
|
// UnmarshalJSON implements the json.Unmarshaler interface. The certificate is
|
||||||
// expected to be a quoted, base64 encoded, openssh wire formatted block of bytes.
|
// expected to be a quoted, base64 encoded, openssh wire formatted block of bytes.
|
||||||
func (c *SSHCertificate) UnmarshalJSON(data []byte) error {
|
func (c *SSHCertificate) UnmarshalJSON(data []byte) error {
|
||||||
|
@ -100,18 +99,43 @@ func (c *SSHCertificate) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the SignSSHRequest.
|
// SSHPublicKey represents a public key in a response object.
|
||||||
func (s *SignSSHRequest) Validate() error {
|
type SSHPublicKey struct {
|
||||||
switch {
|
ssh.PublicKey
|
||||||
case s.CertType != "" && s.CertType != provisioner.SSHUserCert && s.CertType != provisioner.SSHHostCert:
|
}
|
||||||
return errors.Errorf("unknown certType %s", s.CertType)
|
|
||||||
case len(s.PublicKey) == 0:
|
// MarshalJSON implements the json.Marshaler interface. Returns a quoted,
|
||||||
return errors.New("missing or empty publicKey")
|
// base64 encoded, openssh wire format version of the public key.
|
||||||
case len(s.OTT) == 0:
|
func (p *SSHPublicKey) MarshalJSON() ([]byte, error) {
|
||||||
return errors.New("missing or empty ott")
|
if p == nil || p.PublicKey == nil {
|
||||||
default:
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
s := base64.StdEncoding.EncodeToString(p.PublicKey.Marshal())
|
||||||
|
return []byte(`"` + s + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface. The public key is
|
||||||
|
// expected to be a quoted, base64 encoded, openssh wire formatted block of
|
||||||
|
// bytes.
|
||||||
|
func (p *SSHPublicKey) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return errors.Wrap(err, "error decoding ssh public key")
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
p.PublicKey = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
data, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error decoding ssh public key")
|
||||||
|
}
|
||||||
|
pub, err := ssh.ParsePublicKey(data)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error parsing ssh public key")
|
||||||
|
}
|
||||||
|
p.PublicKey = pub
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignSSH is an HTTP handler that reads an SignSSHRequest with a one-time-token
|
// SignSSH is an HTTP handler that reads an SignSSHRequest with a one-time-token
|
||||||
|
@ -182,24 +206,24 @@ func (h *caHandler) SignSSH(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHConfig is an HTTP handler that returns the SSH public keys for user and
|
// SSHKeys is an HTTP handler that returns the SSH public keys for user and host
|
||||||
// host certificates.
|
// certificates.
|
||||||
func (h *caHandler) SSHConfig(w http.ResponseWriter, r *http.Request) {
|
func (h *caHandler) SSHKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
config, err := h.Authority.SSHConfig()
|
keys, err := h.Authority.GetSSHKeys()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, NotFound(err))
|
WriteError(w, NotFound(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var host, user *SSHPublicKey
|
var host, user *SSHPublicKey
|
||||||
if config.HostKey != nil {
|
if keys.HostKey != nil {
|
||||||
host = &SSHPublicKey{config.HostKey}
|
host = &SSHPublicKey{PublicKey: keys.HostKey}
|
||||||
}
|
}
|
||||||
if config.UserKey != nil {
|
if keys.UserKey != nil {
|
||||||
user = &SSHPublicKey{config.UserKey}
|
user = &SSHPublicKey{PublicKey: keys.UserKey}
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON(w, &SSHConfigResponse{
|
JSON(w, &SSHKeysResponse{
|
||||||
HostKey: host,
|
HostKey: host,
|
||||||
UserKey: user,
|
UserKey: user,
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/smallstep/assert"
|
"github.com/smallstep/assert"
|
||||||
|
"github.com/smallstep/certificates/authority"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/certificates/logging"
|
"github.com/smallstep/certificates/logging"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
@ -296,23 +297,75 @@ func Test_caHandler_SignSSH(t *testing.T) {
|
||||||
},
|
},
|
||||||
}).(*caHandler)
|
}).(*caHandler)
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://example.com/sign-ssh", bytes.NewReader(tt.req))
|
req := httptest.NewRequest("POST", "http://example.com/ssh/sign", bytes.NewReader(tt.req))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
h.SignSSH(logging.NewResponseLogger(w), req)
|
h.SignSSH(logging.NewResponseLogger(w), req)
|
||||||
res := w.Result()
|
res := w.Result()
|
||||||
|
|
||||||
if res.StatusCode != tt.statusCode {
|
if res.StatusCode != tt.statusCode {
|
||||||
t.Errorf("caHandler.Root StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
|
t.Errorf("caHandler.SignSSH StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("caHandler.Root unexpected error = %v", err)
|
t.Errorf("caHandler.SignSSH unexpected error = %v", err)
|
||||||
}
|
}
|
||||||
if tt.statusCode < http.StatusBadRequest {
|
if tt.statusCode < http.StatusBadRequest {
|
||||||
if !bytes.Equal(bytes.TrimSpace(body), tt.body) {
|
if !bytes.Equal(bytes.TrimSpace(body), tt.body) {
|
||||||
t.Errorf("caHandler.Root Body = %s, wants %s", body, tt.body)
|
t.Errorf("caHandler.SignSSH Body = %s, wants %s", body, tt.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_caHandler_SSHKeys(t *testing.T) {
|
||||||
|
user, err := ssh.NewPublicKey(sshUserKey.Public())
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
userB64 := base64.StdEncoding.EncodeToString(user.Marshal())
|
||||||
|
|
||||||
|
host, err := ssh.NewPublicKey(sshHostKey.Public())
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
hostB64 := base64.StdEncoding.EncodeToString(host.Marshal())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
keys *authority.SSHKeys
|
||||||
|
keysErr error
|
||||||
|
body []byte
|
||||||
|
statusCode int
|
||||||
|
}{
|
||||||
|
{"ok", &authority.SSHKeys{HostKey: host, UserKey: user}, nil, []byte(fmt.Sprintf(`{"userKey":"%s","hostKey":"%s"}`, userB64, hostB64)), http.StatusOK},
|
||||||
|
{"user", &authority.SSHKeys{UserKey: user}, nil, []byte(fmt.Sprintf(`{"userKey":"%s"}`, userB64)), http.StatusOK},
|
||||||
|
{"host", &authority.SSHKeys{HostKey: host}, nil, []byte(fmt.Sprintf(`{"hostKey":"%s"}`, hostB64)), http.StatusOK},
|
||||||
|
{"error", nil, fmt.Errorf("an error"), nil, http.StatusNotFound},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
h := New(&mockAuthority{
|
||||||
|
getSSHKeys: func() (*authority.SSHKeys, error) {
|
||||||
|
return tt.keys, tt.keysErr
|
||||||
|
},
|
||||||
|
}).(*caHandler)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "http://example.com/ssh/keys", http.NoBody)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
h.SSHKeys(logging.NewResponseLogger(w), req)
|
||||||
|
res := w.Result()
|
||||||
|
|
||||||
|
if res.StatusCode != tt.statusCode {
|
||||||
|
t.Errorf("caHandler.SSHKeys StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("caHandler.SSHKeys unexpected error = %v", err)
|
||||||
|
}
|
||||||
|
if tt.statusCode < http.StatusBadRequest {
|
||||||
|
if !bytes.Equal(bytes.TrimSpace(body), tt.body) {
|
||||||
|
t.Errorf("caHandler.SSHKeys Body = %s, wants %s", body, tt.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,28 +24,28 @@ const (
|
||||||
SSHAddUserCommand = "sudo useradd -m <principal>; nc -q0 localhost 22"
|
SSHAddUserCommand = "sudo useradd -m <principal>; nc -q0 localhost 22"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SSHConfiguration is the return type for SSHConfig.
|
// SSHKeys represents the SSH User and Host public keys.
|
||||||
type SSHConfiguration struct {
|
type SSHKeys struct {
|
||||||
UserKey ssh.PublicKey
|
UserKey ssh.PublicKey
|
||||||
HostKey ssh.PublicKey
|
HostKey ssh.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHConfig returns the SSH User and Host public keys.
|
// GetSSHKeys returns the SSH User and Host public keys.
|
||||||
func (a *Authority) SSHConfig() (*SSHConfiguration, error) {
|
func (a *Authority) GetSSHKeys() (*SSHKeys, error) {
|
||||||
var config SSHConfiguration
|
var keys SSHKeys
|
||||||
if a.sshCAUserCertSignKey != nil {
|
if a.sshCAUserCertSignKey != nil {
|
||||||
config.UserKey = a.sshCAUserCertSignKey.PublicKey()
|
keys.UserKey = a.sshCAUserCertSignKey.PublicKey()
|
||||||
}
|
}
|
||||||
if a.sshCAHostCertSignKey != nil {
|
if a.sshCAHostCertSignKey != nil {
|
||||||
config.HostKey = a.sshCAHostCertSignKey.PublicKey()
|
keys.HostKey = a.sshCAHostCertSignKey.PublicKey()
|
||||||
}
|
}
|
||||||
if config.UserKey == nil && config.HostKey == nil {
|
if keys.UserKey == nil && keys.HostKey == nil {
|
||||||
return nil, &apiError{
|
return nil, &apiError{
|
||||||
err: errors.New("sshConfig: ssh is not configured"),
|
err: errors.New("sshConfig: ssh is not configured"),
|
||||||
code: http.StatusNotFound,
|
code: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &config, nil
|
return &keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignSSH creates a signed SSH certificate with the given public key and options.
|
// SignSSH creates a signed SSH certificate with the given public key and options.
|
||||||
|
|
20
ca/client.go
20
ca/client.go
|
@ -380,7 +380,7 @@ func (c *Client) SignSSH(req *api.SignSSHRequest) (*api.SignSSHResponse, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error marshaling request")
|
return nil, errors.Wrap(err, "error marshaling request")
|
||||||
}
|
}
|
||||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/sign-ssh"})
|
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/sign"})
|
||||||
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body))
|
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||||
|
@ -527,6 +527,24 @@ func (c *Client) Federation() (*api.FederationResponse, error) {
|
||||||
return &federation, nil
|
return &federation, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSHKeys performs the get ssh keys request to the CA and returns the
|
||||||
|
// api.SSHKeysResponse struct.
|
||||||
|
func (c *Client) SSHKeys() (*api.SSHKeysResponse, error) {
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/keys"})
|
||||||
|
resp, err := c.client.Get(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
return nil, readError(resp.Body)
|
||||||
|
}
|
||||||
|
var keys api.SSHKeysResponse
|
||||||
|
if err := readJSON(resp.Body, &keys); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||||
|
}
|
||||||
|
return &keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RootFingerprint is a helper method that returns the current root fingerprint.
|
// RootFingerprint is a helper method that returns the current root fingerprint.
|
||||||
// It does an health connection and gets the fingerprint from the TLS verified
|
// It does an health connection and gets the fingerprint from the TLS verified
|
||||||
// chains.
|
// chains.
|
||||||
|
|
|
@ -2,6 +2,9 @@ package ca
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
@ -17,6 +20,7 @@ import (
|
||||||
"github.com/smallstep/certificates/api"
|
"github.com/smallstep/certificates/api"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/cli/crypto/x509util"
|
"github.com/smallstep/cli/crypto/x509util"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -96,6 +100,14 @@ DCbKzWTW8lqVdp9Kyf7XEhhc2R8C5w==
|
||||||
-----END CERTIFICATE REQUEST-----`
|
-----END CERTIFICATE REQUEST-----`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func mustKey() *ecdsa.PrivateKey {
|
||||||
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return priv
|
||||||
|
}
|
||||||
|
|
||||||
func parseCertificate(data string) *x509.Certificate {
|
func parseCertificate(data string) *x509.Certificate {
|
||||||
block, _ := pem.Decode([]byte(data))
|
block, _ := pem.Decode([]byte(data))
|
||||||
if block == nil {
|
if block == nil {
|
||||||
|
@ -710,6 +722,67 @@ func TestClient_Federation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClient_SSHKeys(t *testing.T) {
|
||||||
|
key, err := ssh.NewPublicKey(mustKey().Public())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := &api.SSHKeysResponse{
|
||||||
|
HostKey: &api.SSHPublicKey{PublicKey: key},
|
||||||
|
UserKey: &api.SSHPublicKey{PublicKey: key},
|
||||||
|
}
|
||||||
|
notFound := api.NotFound(fmt.Errorf("Not Found"))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
response interface{}
|
||||||
|
responseCode int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", ok, 200, false},
|
||||||
|
{"not found", notFound, 404, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := httptest.NewServer(nil)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewClient() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
api.JSONStatus(w, tt.response, tt.responseCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
got, err := c.SSHKeys()
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
fmt.Printf("%+v", err)
|
||||||
|
t.Errorf("Client.SSHKeys() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("Client.SSHKeys() = %v, want nil", got)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(err, tt.response) {
|
||||||
|
t.Errorf("Client.SSHKeys() error = %v, want %v", err, tt.response)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !reflect.DeepEqual(got, tt.response) {
|
||||||
|
t.Errorf("Client.SSHKeys() = %v, want %v", got, tt.response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_parseEndpoint(t *testing.T) {
|
func Test_parseEndpoint(t *testing.T) {
|
||||||
expected1 := &url.URL{Scheme: "https", Host: "ca.smallstep.com"}
|
expected1 := &url.URL{Scheme: "https", Host: "ca.smallstep.com"}
|
||||||
expected2 := &url.URL{Scheme: "https", Host: "ca.smallstep.com", Path: "/1.0/sign"}
|
expected2 := &url.URL{Scheme: "https", Host: "ca.smallstep.com", Path: "/1.0/sign"}
|
||||||
|
|
Loading…
Reference in a new issue