forked from TrueCloudLab/certificates
Move provisioner marshaling logic to api package
This commit is contained in:
parent
f2e1c56c6c
commit
c365d8580e
4 changed files with 127 additions and 114 deletions
35
api/api.go
35
api/api.go
|
@ -224,8 +224,39 @@ type RootResponse struct {
|
||||||
// ProvisionersResponse is the response object that returns the list of
|
// ProvisionersResponse is the response object that returns the list of
|
||||||
// provisioners.
|
// provisioners.
|
||||||
type ProvisionersResponse struct {
|
type ProvisionersResponse struct {
|
||||||
Provisioners provisioner.List `json:"provisioners"`
|
Provisioners provisioner.List
|
||||||
NextCursor string `json:"nextCursor"`
|
NextCursor string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler. It marshals the ProvisionersResponse
|
||||||
|
// into a byte slice.
|
||||||
|
//
|
||||||
|
// Special treatment is given to the SCEP provisioner, as it contains a
|
||||||
|
// challenge secret that MUST NOT be leaked in (public) HTTP responses. The
|
||||||
|
// challenge value is thus redacted in HTTP responses.
|
||||||
|
func (p ProvisionersResponse) MarshalJSON() ([]byte, error) {
|
||||||
|
for _, item := range p.Provisioners {
|
||||||
|
scepProv, ok := item.(*provisioner.SCEP)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
old := scepProv.ChallengePassword
|
||||||
|
scepProv.ChallengePassword = "*** REDACTED ***"
|
||||||
|
defer func(p string) { //nolint:gocritic // defer in loop required to restore initial state of provisioners
|
||||||
|
scepProv.ChallengePassword = p
|
||||||
|
}(old)
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = struct {
|
||||||
|
Provisioners []provisioner.Interface `json:"provisioners"`
|
||||||
|
NextCursor string `json:"nextCursor"`
|
||||||
|
}{
|
||||||
|
Provisioners: []provisioner.Interface(p.Provisioners),
|
||||||
|
NextCursor: p.NextCursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvisionerKeyResponse is the response object that returns the encrypted key
|
// ProvisionerKeyResponse is the response object that returns the encrypted key
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/dsa" //nolint
|
"crypto/dsa" //nolint:staticcheck // support legacy algorithms
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
@ -28,7 +28,9 @@ import (
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
sassert "github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
squarejose "gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/x509util"
|
"go.step.sm/crypto/x509util"
|
||||||
|
@ -1564,3 +1566,94 @@ func mustCertificate(t *testing.T, pub, priv interface{}) *x509.Certificate {
|
||||||
}
|
}
|
||||||
return cert
|
return cert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProvisionersResponse_MarshalJSON(t *testing.T) {
|
||||||
|
|
||||||
|
k := map[string]any{
|
||||||
|
"use": "sig",
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
|
||||||
|
"crv": "P-256",
|
||||||
|
"alg": "ES256",
|
||||||
|
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
|
||||||
|
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
|
||||||
|
}
|
||||||
|
key := squarejose.JSONWebKey{}
|
||||||
|
b, err := json.Marshal(k)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
err = json.Unmarshal(b, &key)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
r := ProvisionersResponse{
|
||||||
|
Provisioners: provisioner.List{
|
||||||
|
&provisioner.SCEP{
|
||||||
|
Name: "scep",
|
||||||
|
Type: "scep",
|
||||||
|
ChallengePassword: "not-so-secret",
|
||||||
|
MinimumPublicKeyLength: 2048,
|
||||||
|
EncryptionAlgorithmIdentifier: 2,
|
||||||
|
},
|
||||||
|
&provisioner.JWK{
|
||||||
|
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
|
||||||
|
Key: &key,
|
||||||
|
Name: "step-cli",
|
||||||
|
Type: "JWK",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NextCursor: "next",
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]any{
|
||||||
|
"provisioners": []map[string]any{
|
||||||
|
{
|
||||||
|
"type": "scep",
|
||||||
|
"name": "scep",
|
||||||
|
"challenge": "*** REDACTED ***",
|
||||||
|
"minimumPublicKeyLength": 2048,
|
||||||
|
"encryptionAlgorithmIdentifier": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "JWK",
|
||||||
|
"name": "step-cli",
|
||||||
|
"key": map[string]any{
|
||||||
|
"use": "sig",
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
|
||||||
|
"crv": "P-256",
|
||||||
|
"alg": "ES256",
|
||||||
|
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
|
||||||
|
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
|
||||||
|
},
|
||||||
|
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nextCursor": "next",
|
||||||
|
}
|
||||||
|
|
||||||
|
expBytes, err := json.Marshal(expected)
|
||||||
|
sassert.NoError(t, err)
|
||||||
|
|
||||||
|
br, err := r.MarshalJSON()
|
||||||
|
sassert.NoError(t, err)
|
||||||
|
sassert.JSONEq(t, string(expBytes), string(br))
|
||||||
|
|
||||||
|
keyCopy := key
|
||||||
|
expList := provisioner.List{
|
||||||
|
&provisioner.SCEP{
|
||||||
|
Name: "scep",
|
||||||
|
Type: "scep",
|
||||||
|
ChallengePassword: "not-so-secret",
|
||||||
|
MinimumPublicKeyLength: 2048,
|
||||||
|
EncryptionAlgorithmIdentifier: 2,
|
||||||
|
},
|
||||||
|
&provisioner.JWK{
|
||||||
|
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
|
||||||
|
Key: &keyCopy,
|
||||||
|
Name: "step-cli",
|
||||||
|
Type: "JWK",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON must not affect the struct properties itself
|
||||||
|
sassert.Equal(t, expList, r.Provisioners)
|
||||||
|
}
|
||||||
|
|
|
@ -235,29 +235,6 @@ type provisioner struct {
|
||||||
// List represents a list of provisioners.
|
// List represents a list of provisioners.
|
||||||
type List []Interface
|
type List []Interface
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler. It marshals a List of Interfaces
|
|
||||||
// into a byte slice.
|
|
||||||
//
|
|
||||||
// Special treatment is given to the SCEP provisioner, as it contains a
|
|
||||||
// challenge secret that MUST NOT be leaked in public HTTP responses. The
|
|
||||||
// challenge value is redacted in HTTP responses.
|
|
||||||
func (l List) MarshalJSON() ([]byte, error) {
|
|
||||||
for _, item := range l {
|
|
||||||
scepProv, ok := item.(*SCEP)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
old := scepProv.ChallengePassword
|
|
||||||
scepProv.ChallengePassword = "*** REDACTED ***"
|
|
||||||
defer func(p string) {
|
|
||||||
scepProv.ChallengePassword = p
|
|
||||||
}(old)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal([]Interface(l))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler and allows to unmarshal a list of a
|
// UnmarshalJSON implements json.Unmarshaler and allows to unmarshal a list of a
|
||||||
// interfaces into the right type.
|
// interfaces into the right type.
|
||||||
func (l *List) UnmarshalJSON(data []byte) error {
|
func (l *List) UnmarshalJSON(data []byte) error {
|
||||||
|
|
|
@ -2,14 +2,11 @@ package provisioner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
sassert "github.com/stretchr/testify/assert"
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
squarejose "gopkg.in/square/go-jose.v2"
|
|
||||||
|
|
||||||
"github.com/smallstep/assert"
|
"github.com/smallstep/assert"
|
||||||
"github.com/smallstep/certificates/api/render"
|
"github.com/smallstep/certificates/api/render"
|
||||||
|
@ -252,88 +249,3 @@ func TestUnimplementedMethods(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestList_MarshalJSON(t *testing.T) {
|
|
||||||
|
|
||||||
k := map[string]any{
|
|
||||||
"use": "sig",
|
|
||||||
"kty": "EC",
|
|
||||||
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
|
|
||||||
"crv": "P-256",
|
|
||||||
"alg": "ES256",
|
|
||||||
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
|
|
||||||
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
|
|
||||||
}
|
|
||||||
key := squarejose.JSONWebKey{}
|
|
||||||
b, err := json.Marshal(k)
|
|
||||||
assert.FatalError(t, err)
|
|
||||||
err = json.Unmarshal(b, &key)
|
|
||||||
assert.FatalError(t, err)
|
|
||||||
|
|
||||||
l := List{
|
|
||||||
&SCEP{
|
|
||||||
Name: "scep",
|
|
||||||
Type: "scep",
|
|
||||||
ChallengePassword: "not-so-secret",
|
|
||||||
MinimumPublicKeyLength: 2048,
|
|
||||||
EncryptionAlgorithmIdentifier: 0,
|
|
||||||
},
|
|
||||||
&JWK{
|
|
||||||
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
|
|
||||||
Key: &key,
|
|
||||||
Name: "step-cli",
|
|
||||||
Type: "JWK",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []map[string]any{
|
|
||||||
{
|
|
||||||
"type": "scep",
|
|
||||||
"name": "scep",
|
|
||||||
"challenge": "*** REDACTED ***",
|
|
||||||
"minimumPublicKeyLength": 2048,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "JWK",
|
|
||||||
"name": "step-cli",
|
|
||||||
"key": map[string]any{
|
|
||||||
"use": "sig",
|
|
||||||
"kty": "EC",
|
|
||||||
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
|
|
||||||
"crv": "P-256",
|
|
||||||
"alg": "ES256",
|
|
||||||
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
|
|
||||||
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
|
|
||||||
},
|
|
||||||
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
expBytes, err := json.Marshal(expected)
|
|
||||||
sassert.NoError(t, err)
|
|
||||||
|
|
||||||
bl, err := l.MarshalJSON()
|
|
||||||
sassert.NoError(t, err)
|
|
||||||
sassert.JSONEq(t, string(expBytes), string(bl))
|
|
||||||
|
|
||||||
keyCopy := key
|
|
||||||
expList := List{
|
|
||||||
&SCEP{
|
|
||||||
Name: "scep",
|
|
||||||
Type: "scep",
|
|
||||||
ChallengePassword: "not-so-secret",
|
|
||||||
MinimumPublicKeyLength: 2048,
|
|
||||||
EncryptionAlgorithmIdentifier: 0,
|
|
||||||
},
|
|
||||||
&JWK{
|
|
||||||
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
|
|
||||||
Key: &keyCopy,
|
|
||||||
Name: "step-cli",
|
|
||||||
Type: "JWK",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON must not affect the struct properties itself
|
|
||||||
sassert.Equal(t, expList, l)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue