-
Notifications
You must be signed in to change notification settings - Fork 0
/
ssh.go
159 lines (141 loc) · 4.8 KB
/
ssh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package api
import (
"context"
"encoding/base64"
"encoding/json"
"net/http"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
"golang.org/x/crypto/ssh"
)
// SSHAuthority is the interface implemented by a SSH CA authority.
type SSHAuthority interface {
SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
}
// SignSSHRequest is the request body of an SSH certificate request.
type SignSSHRequest struct {
PublicKey []byte `json:"publicKey"` //base64 encoded
OTT string `json:"ott"`
CertType string `json:"certType,omitempty"`
Principals []string `json:"principals,omitempty"`
ValidAfter TimeDuration `json:"validAfter,omitempty"`
ValidBefore TimeDuration `json:"validBefore,omitempty"`
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
}
// SignSSHResponse is the response object that returns the SSH certificate.
type SignSSHResponse struct {
Certificate SSHCertificate `json:"crt"`
AddUserCertificate *SSHCertificate `json:"addUserCrt,omitempty"`
}
// SSHCertificate represents the response SSH certificate.
type SSHCertificate struct {
*ssh.Certificate `json:"omitempty"`
}
// MarshalJSON implements the json.Marshaler interface. Returns a quoted,
// base64 encoded, openssh wire format version of the certificate.
func (c SSHCertificate) MarshalJSON() ([]byte, error) {
if c.Certificate == nil {
return []byte("null"), nil
}
s := base64.StdEncoding.EncodeToString(c.Certificate.Marshal())
return []byte(`"` + s + `"`), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface. The certificate is
// expected to be a quoted, base64 encoded, openssh wire formatted block of bytes.
func (c *SSHCertificate) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return errors.Wrap(err, "error decoding certificate")
}
if s == "" {
c.Certificate = nil
return nil
}
certData, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return errors.Wrap(err, "error decoding ssh certificate")
}
pub, err := ssh.ParsePublicKey(certData)
if err != nil {
return errors.Wrap(err, "error parsing ssh certificate")
}
cert, ok := pub.(*ssh.Certificate)
if !ok {
return errors.Errorf("error decoding ssh certificate: %T is not an *ssh.Certificate", pub)
}
c.Certificate = cert
return nil
}
// 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
}
}
// SignSSH is an HTTP handler that reads an SignSSHRequest with a one-time-token
// (ott) from the body and creates a new SSH certificate with the information in
// the request.
func (h *caHandler) SignSSH(w http.ResponseWriter, r *http.Request) {
var body SignSSHRequest
if err := ReadJSON(r.Body, &body); err != nil {
WriteError(w, BadRequest(errors.Wrap(err, "error reading request body")))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
WriteError(w, BadRequest(err))
return
}
publicKey, err := ssh.ParsePublicKey(body.PublicKey)
if err != nil {
WriteError(w, BadRequest(errors.Wrap(err, "error parsing publicKey")))
return
}
var addUserPublicKey ssh.PublicKey
if body.AddUserPublicKey != nil {
addUserPublicKey, err = ssh.ParsePublicKey(body.AddUserPublicKey)
if err != nil {
WriteError(w, BadRequest(errors.Wrap(err, "error parsing addUserPublicKey")))
return
}
}
opts := provisioner.SSHOptions{
CertType: body.CertType,
Principals: body.Principals,
ValidBefore: body.ValidBefore,
ValidAfter: body.ValidAfter,
}
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.SignSSHMethod)
signOpts, err := h.Authority.Authorize(ctx, body.OTT)
if err != nil {
WriteError(w, Unauthorized(err))
return
}
cert, err := h.Authority.SignSSH(publicKey, opts, signOpts...)
if err != nil {
WriteError(w, Forbidden(err))
return
}
var addUserCertificate *SSHCertificate
if addUserPublicKey != nil && cert.CertType == ssh.UserCert && len(cert.ValidPrincipals) == 1 {
addUserCert, err := h.Authority.SignSSHAddUser(addUserPublicKey, cert)
if err != nil {
WriteError(w, Forbidden(err))
return
}
addUserCertificate = &SSHCertificate{addUserCert}
}
w.WriteHeader(http.StatusCreated)
JSON(w, &SignSSHResponse{
Certificate: SSHCertificate{cert},
AddUserCertificate: addUserCertificate,
})
}