forked from TrueCloudLab/certificates
Add identity certificate in ssh response.
This commit is contained in:
parent
bbaf8e106e
commit
11c8639782
4 changed files with 80 additions and 32 deletions
60
api/ssh.go
60
api/ssh.go
|
@ -29,14 +29,15 @@ type SSHAuthority interface {
|
||||||
|
|
||||||
// SSHSignRequest is the request body of an SSH certificate request.
|
// SSHSignRequest is the request body of an SSH certificate request.
|
||||||
type SSHSignRequest struct {
|
type SSHSignRequest struct {
|
||||||
PublicKey []byte `json:"publicKey"` //base64 encoded
|
PublicKey []byte `json:"publicKey"` // base64 encoded
|
||||||
OTT string `json:"ott"`
|
OTT string `json:"ott"`
|
||||||
CertType string `json:"certType,omitempty"`
|
CertType string `json:"certType,omitempty"`
|
||||||
Principals []string `json:"principals,omitempty"`
|
Principals []string `json:"principals,omitempty"`
|
||||||
ValidAfter TimeDuration `json:"validAfter,omitempty"`
|
ValidAfter TimeDuration `json:"validAfter,omitempty"`
|
||||||
ValidBefore TimeDuration `json:"validBefore,omitempty"`
|
ValidBefore TimeDuration `json:"validBefore,omitempty"`
|
||||||
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
|
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
|
||||||
KeyID string `json:"keyID"`
|
KeyID string `json:"keyID"`
|
||||||
|
IdentityCSR CertificateRequest `json:"identityCSR,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the SSHSignRequest.
|
// Validate validates the SSHSignRequest.
|
||||||
|
@ -49,14 +50,21 @@ func (s *SSHSignRequest) Validate() error {
|
||||||
case len(s.OTT) == 0:
|
case len(s.OTT) == 0:
|
||||||
return errors.New("missing or empty ott")
|
return errors.New("missing or empty ott")
|
||||||
default:
|
default:
|
||||||
|
// Validate identity signature if provided
|
||||||
|
if s.IdentityCSR.CertificateRequest != nil {
|
||||||
|
if err := s.IdentityCSR.CertificateRequest.CheckSignature(); err != nil {
|
||||||
|
return errors.Wrap(err, "invalid csr")
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHSignResponse is the response object that returns the SSH certificate.
|
// SSHSignResponse is the response object that returns the SSH certificate.
|
||||||
type SSHSignResponse struct {
|
type SSHSignResponse struct {
|
||||||
Certificate SSHCertificate `json:"crt"`
|
Certificate SSHCertificate `json:"crt"`
|
||||||
AddUserCertificate *SSHCertificate `json:"addUserCrt,omitempty"`
|
AddUserCertificate *SSHCertificate `json:"addUserCrt,omitempty"`
|
||||||
|
IdentityCertificate []Certificate `json:"identityCrt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHRootsResponse represents the response object that returns the SSH user and
|
// SSHRootsResponse represents the response object that returns the SSH user and
|
||||||
|
@ -292,11 +300,33 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) {
|
||||||
addUserCertificate = &SSHCertificate{addUserCert}
|
addUserCertificate = &SSHCertificate{addUserCert}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
// Sign identity certificate if available.
|
||||||
JSON(w, &SSHSignResponse{
|
var identityCertificate []Certificate
|
||||||
Certificate: SSHCertificate{cert},
|
if cr := body.IdentityCSR.CertificateRequest; cr != nil {
|
||||||
AddUserCertificate: addUserCertificate,
|
opts := provisioner.Options{
|
||||||
})
|
NotBefore: body.ValidAfter,
|
||||||
|
NotAfter: body.ValidBefore,
|
||||||
|
}
|
||||||
|
ctx := authority.NewContextWithSkipTokenReuse(context.Background())
|
||||||
|
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
|
||||||
|
signOpts, err := h.Authority.Authorize(ctx, body.OTT)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(w, Unauthorized(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certChain, err := h.Authority.Sign(cr, opts, signOpts...)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(w, Forbidden(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
identityCertificate = certChainToPEM(certChain)
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONStatus(w, &SSHSignResponse{
|
||||||
|
Certificate: SSHCertificate{cert},
|
||||||
|
AddUserCertificate: addUserCertificate,
|
||||||
|
IdentityCertificate: identityCertificate,
|
||||||
|
}, http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHRoots is an HTTP handler that returns the SSH public keys for user and host
|
// SSHRoots is an HTTP handler that returns the SSH public keys for user and host
|
||||||
|
|
|
@ -19,10 +19,24 @@ type Claims struct {
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type skipTokenReuseKey struct{}
|
||||||
|
|
||||||
|
// NewContextWithSkipTokenReuse creates a new context from ctx and attaches a
|
||||||
|
// value to skip the token reuse.
|
||||||
|
func NewContextWithSkipTokenReuse(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, skipTokenReuseKey{}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipTokenReuseFromContext returns if the token reuse needs to be ignored.
|
||||||
|
func SkipTokenReuseFromContext(ctx context.Context) bool {
|
||||||
|
m, _ := ctx.Value(skipTokenReuseKey{}).(bool)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// authorizeToken parses the token and returns the provisioner used to generate
|
// authorizeToken parses the token and returns the provisioner used to generate
|
||||||
// the token. This method enforces the One-Time use policy (tokens can only be
|
// the token. This method enforces the One-Time use policy (tokens can only be
|
||||||
// used once).
|
// used once).
|
||||||
func (a *Authority) authorizeToken(ott string) (provisioner.Interface, error) {
|
func (a *Authority) authorizeToken(ctx context.Context, ott string) (provisioner.Interface, error) {
|
||||||
var errContext = map[string]interface{}{"ott": ott}
|
var errContext = map[string]interface{}{"ott": ott}
|
||||||
|
|
||||||
// Validate payload
|
// Validate payload
|
||||||
|
@ -58,15 +72,17 @@ func (a *Authority) authorizeToken(ott string) (provisioner.Interface, error) {
|
||||||
http.StatusUnauthorized, errContext}
|
http.StatusUnauthorized, errContext}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the token to protect against reuse.
|
// Store the token to protect against reuse unless it's skipped.
|
||||||
if reuseKey, err := p.GetTokenID(ott); err == nil {
|
if !SkipTokenReuseFromContext(ctx) {
|
||||||
ok, err := a.db.UseToken(reuseKey, ott)
|
if reuseKey, err := p.GetTokenID(ott); err == nil {
|
||||||
if err != nil {
|
ok, err := a.db.UseToken(reuseKey, ott)
|
||||||
return nil, &apiError{errors.Wrap(err, "authorizeToken: failed when checking if token already used"),
|
if err != nil {
|
||||||
http.StatusInternalServerError, errContext}
|
return nil, &apiError{errors.Wrap(err, "authorizeToken: failed when checking if token already used"),
|
||||||
}
|
http.StatusInternalServerError, errContext}
|
||||||
if !ok {
|
}
|
||||||
return nil, &apiError{errors.Errorf("authorizeToken: token already used"), http.StatusUnauthorized, errContext}
|
if !ok {
|
||||||
|
return nil, &apiError{errors.Errorf("authorizeToken: token already used"), http.StatusUnauthorized, errContext}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +132,7 @@ func (a *Authority) Authorize(ctx context.Context, ott string) ([]provisioner.Si
|
||||||
// list of methods to apply to the signing flow.
|
// list of methods to apply to the signing flow.
|
||||||
func (a *Authority) authorizeSign(ctx context.Context, ott string) ([]provisioner.SignOption, error) {
|
func (a *Authority) authorizeSign(ctx context.Context, ott string) ([]provisioner.SignOption, error) {
|
||||||
var errContext = apiCtx{"ott": ott}
|
var errContext = apiCtx{"ott": ott}
|
||||||
p, err := a.authorizeToken(ott)
|
p, err := a.authorizeToken(ctx, ott)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &apiError{errors.Wrap(err, "authorizeSign"), http.StatusUnauthorized, errContext}
|
return nil, &apiError{errors.Wrap(err, "authorizeSign"), http.StatusUnauthorized, errContext}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +159,7 @@ func (a *Authority) AuthorizeSign(ott string) ([]provisioner.SignOption, error)
|
||||||
func (a *Authority) authorizeRevoke(ctx context.Context, token string) error {
|
func (a *Authority) authorizeRevoke(ctx context.Context, token string) error {
|
||||||
errContext := map[string]interface{}{"ott": token}
|
errContext := map[string]interface{}{"ott": token}
|
||||||
|
|
||||||
p, err := a.authorizeToken(token)
|
p, err := a.authorizeToken(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &apiError{errors.Wrap(err, "authorizeRevoke"), http.StatusUnauthorized, errContext}
|
return &apiError{errors.Wrap(err, "authorizeRevoke"), http.StatusUnauthorized, errContext}
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,7 +198,9 @@ func (c *Config) getAudiences() provisioner.Audiences {
|
||||||
for _, name := range c.DNSNames {
|
for _, name := range c.DNSNames {
|
||||||
audiences.Sign = append(audiences.Sign,
|
audiences.Sign = append(audiences.Sign,
|
||||||
fmt.Sprintf("https://%s/1.0/sign", name),
|
fmt.Sprintf("https://%s/1.0/sign", name),
|
||||||
fmt.Sprintf("https://%s/sign", name))
|
fmt.Sprintf("https://%s/sign", name),
|
||||||
|
fmt.Sprintf("https://%s/1.0/ssh/sign", name),
|
||||||
|
fmt.Sprintf("https://%s/ssh/sign", name))
|
||||||
audiences.Revoke = append(audiences.Revoke,
|
audiences.Revoke = append(audiences.Revoke,
|
||||||
fmt.Sprintf("https://%s/1.0/revoke", name),
|
fmt.Sprintf("https://%s/1.0/revoke", name),
|
||||||
fmt.Sprintf("https://%s/revoke", name))
|
fmt.Sprintf("https://%s/revoke", name))
|
||||||
|
|
|
@ -190,7 +190,7 @@ func (a *Authority) GetSSHBastion(user string, hostname string) (*Bastion, error
|
||||||
// list of methods to apply to the signing flow.
|
// list of methods to apply to the signing flow.
|
||||||
func (a *Authority) authorizeSSHSign(ctx context.Context, ott string) ([]provisioner.SignOption, error) {
|
func (a *Authority) authorizeSSHSign(ctx context.Context, ott string) ([]provisioner.SignOption, error) {
|
||||||
var errContext = apiCtx{"ott": ott}
|
var errContext = apiCtx{"ott": ott}
|
||||||
p, err := a.authorizeToken(ott)
|
p, err := a.authorizeToken(ctx, ott)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &apiError{errors.Wrap(err, "authorizeSSHSign"), http.StatusUnauthorized, errContext}
|
return nil, &apiError{errors.Wrap(err, "authorizeSSHSign"), http.StatusUnauthorized, errContext}
|
||||||
}
|
}
|
||||||
|
@ -325,7 +325,7 @@ func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, sign
|
||||||
func (a *Authority) authorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) {
|
func (a *Authority) authorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) {
|
||||||
errContext := map[string]interface{}{"ott": token}
|
errContext := map[string]interface{}{"ott": token}
|
||||||
|
|
||||||
p, err := a.authorizeToken(token)
|
p, err := a.authorizeToken(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &apiError{
|
return nil, &apiError{
|
||||||
err: errors.Wrap(err, "authorizeSSHRenew"),
|
err: errors.Wrap(err, "authorizeSSHRenew"),
|
||||||
|
@ -435,7 +435,7 @@ func (a *Authority) RenewSSH(oldCert *ssh.Certificate) (*ssh.Certificate, error)
|
||||||
func (a *Authority) authorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []provisioner.SignOption, error) {
|
func (a *Authority) authorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []provisioner.SignOption, error) {
|
||||||
errContext := map[string]interface{}{"ott": token}
|
errContext := map[string]interface{}{"ott": token}
|
||||||
|
|
||||||
p, err := a.authorizeToken(token)
|
p, err := a.authorizeToken(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, &apiError{
|
return nil, nil, &apiError{
|
||||||
err: errors.Wrap(err, "authorizeSSHRenew"),
|
err: errors.Wrap(err, "authorizeSSHRenew"),
|
||||||
|
@ -567,7 +567,7 @@ func (a *Authority) RekeySSH(oldCert *ssh.Certificate, pub ssh.PublicKey, signOp
|
||||||
func (a *Authority) authorizeSSHRevoke(ctx context.Context, token string) error {
|
func (a *Authority) authorizeSSHRevoke(ctx context.Context, token string) error {
|
||||||
errContext := map[string]interface{}{"ott": token}
|
errContext := map[string]interface{}{"ott": token}
|
||||||
|
|
||||||
p, err := a.authorizeToken(token)
|
p, err := a.authorizeToken(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &apiError{errors.Wrap(err, "authorizeSSHRevoke"), http.StatusUnauthorized, errContext}
|
return &apiError{errors.Wrap(err, "authorizeSSHRevoke"), http.StatusUnauthorized, errContext}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue