Skip to content

Commit c365d85

Browse files
committed
Move provisioner marshaling logic to api package
1 parent f2e1c56 commit c365d85

File tree

4 files changed

+127
-114
lines changed

4 files changed

+127
-114
lines changed

api/api.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,39 @@ type RootResponse struct {
224224
// ProvisionersResponse is the response object that returns the list of
225225
// provisioners.
226226
type ProvisionersResponse struct {
227-
Provisioners provisioner.List `json:"provisioners"`
228-
NextCursor string `json:"nextCursor"`
227+
Provisioners provisioner.List
228+
NextCursor string
229+
}
230+
231+
// MarshalJSON implements json.Marshaler. It marshals the ProvisionersResponse
232+
// into a byte slice.
233+
//
234+
// Special treatment is given to the SCEP provisioner, as it contains a
235+
// challenge secret that MUST NOT be leaked in (public) HTTP responses. The
236+
// challenge value is thus redacted in HTTP responses.
237+
func (p ProvisionersResponse) MarshalJSON() ([]byte, error) {
238+
for _, item := range p.Provisioners {
239+
scepProv, ok := item.(*provisioner.SCEP)
240+
if !ok {
241+
continue
242+
}
243+
244+
old := scepProv.ChallengePassword
245+
scepProv.ChallengePassword = "*** REDACTED ***"
246+
defer func(p string) { //nolint:gocritic // defer in loop required to restore initial state of provisioners
247+
scepProv.ChallengePassword = p
248+
}(old)
249+
}
250+
251+
var list = struct {
252+
Provisioners []provisioner.Interface `json:"provisioners"`
253+
NextCursor string `json:"nextCursor"`
254+
}{
255+
Provisioners: []provisioner.Interface(p.Provisioners),
256+
NextCursor: p.NextCursor,
257+
}
258+
259+
return json.Marshal(list)
229260
}
230261

231262
// ProvisionerKeyResponse is the response object that returns the encrypted key

api/api_test.go

+94-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"bytes"
55
"context"
66
"crypto"
7-
"crypto/dsa" //nolint
7+
"crypto/dsa" //nolint:staticcheck // support legacy algorithms
88
"crypto/ecdsa"
99
"crypto/ed25519"
1010
"crypto/elliptic"
@@ -28,7 +28,9 @@ import (
2828

2929
"github.com/go-chi/chi"
3030
"github.com/pkg/errors"
31+
sassert "github.com/stretchr/testify/assert"
3132
"golang.org/x/crypto/ssh"
33+
squarejose "gopkg.in/square/go-jose.v2"
3234

3335
"go.step.sm/crypto/jose"
3436
"go.step.sm/crypto/x509util"
@@ -1564,3 +1566,94 @@ func mustCertificate(t *testing.T, pub, priv interface{}) *x509.Certificate {
15641566
}
15651567
return cert
15661568
}
1569+
1570+
func TestProvisionersResponse_MarshalJSON(t *testing.T) {
1571+
1572+
k := map[string]any{
1573+
"use": "sig",
1574+
"kty": "EC",
1575+
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
1576+
"crv": "P-256",
1577+
"alg": "ES256",
1578+
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
1579+
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
1580+
}
1581+
key := squarejose.JSONWebKey{}
1582+
b, err := json.Marshal(k)
1583+
assert.FatalError(t, err)
1584+
err = json.Unmarshal(b, &key)
1585+
assert.FatalError(t, err)
1586+
1587+
r := ProvisionersResponse{
1588+
Provisioners: provisioner.List{
1589+
&provisioner.SCEP{
1590+
Name: "scep",
1591+
Type: "scep",
1592+
ChallengePassword: "not-so-secret",
1593+
MinimumPublicKeyLength: 2048,
1594+
EncryptionAlgorithmIdentifier: 2,
1595+
},
1596+
&provisioner.JWK{
1597+
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
1598+
Key: &key,
1599+
Name: "step-cli",
1600+
Type: "JWK",
1601+
},
1602+
},
1603+
NextCursor: "next",
1604+
}
1605+
1606+
expected := map[string]any{
1607+
"provisioners": []map[string]any{
1608+
{
1609+
"type": "scep",
1610+
"name": "scep",
1611+
"challenge": "*** REDACTED ***",
1612+
"minimumPublicKeyLength": 2048,
1613+
"encryptionAlgorithmIdentifier": 2,
1614+
},
1615+
{
1616+
"type": "JWK",
1617+
"name": "step-cli",
1618+
"key": map[string]any{
1619+
"use": "sig",
1620+
"kty": "EC",
1621+
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
1622+
"crv": "P-256",
1623+
"alg": "ES256",
1624+
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
1625+
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
1626+
},
1627+
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
1628+
},
1629+
},
1630+
"nextCursor": "next",
1631+
}
1632+
1633+
expBytes, err := json.Marshal(expected)
1634+
sassert.NoError(t, err)
1635+
1636+
br, err := r.MarshalJSON()
1637+
sassert.NoError(t, err)
1638+
sassert.JSONEq(t, string(expBytes), string(br))
1639+
1640+
keyCopy := key
1641+
expList := provisioner.List{
1642+
&provisioner.SCEP{
1643+
Name: "scep",
1644+
Type: "scep",
1645+
ChallengePassword: "not-so-secret",
1646+
MinimumPublicKeyLength: 2048,
1647+
EncryptionAlgorithmIdentifier: 2,
1648+
},
1649+
&provisioner.JWK{
1650+
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
1651+
Key: &keyCopy,
1652+
Name: "step-cli",
1653+
Type: "JWK",
1654+
},
1655+
}
1656+
1657+
// MarshalJSON must not affect the struct properties itself
1658+
sassert.Equal(t, expList, r.Provisioners)
1659+
}

authority/provisioner/provisioner.go

-23
Original file line numberDiff line numberDiff line change
@@ -235,29 +235,6 @@ type provisioner struct {
235235
// List represents a list of provisioners.
236236
type List []Interface
237237

238-
// MarshalJSON implements json.Marshaler. It marshals a List of Interfaces
239-
// into a byte slice.
240-
//
241-
// Special treatment is given to the SCEP provisioner, as it contains a
242-
// challenge secret that MUST NOT be leaked in public HTTP responses. The
243-
// challenge value is redacted in HTTP responses.
244-
func (l List) MarshalJSON() ([]byte, error) {
245-
for _, item := range l {
246-
scepProv, ok := item.(*SCEP)
247-
if !ok {
248-
continue
249-
}
250-
251-
old := scepProv.ChallengePassword
252-
scepProv.ChallengePassword = "*** REDACTED ***"
253-
defer func(p string) {
254-
scepProv.ChallengePassword = p
255-
}(old)
256-
}
257-
258-
return json.Marshal([]Interface(l))
259-
}
260-
261238
// UnmarshalJSON implements json.Unmarshaler and allows to unmarshal a list of a
262239
// interfaces into the right type.
263240
func (l *List) UnmarshalJSON(data []byte) error {

authority/provisioner/provisioner_test.go

-88
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@ package provisioner
22

33
import (
44
"context"
5-
"encoding/json"
65
"errors"
76
"net/http"
87
"testing"
98

10-
sassert "github.com/stretchr/testify/assert"
119
"golang.org/x/crypto/ssh"
12-
squarejose "gopkg.in/square/go-jose.v2"
1310

1411
"github.com/smallstep/assert"
1512
"github.com/smallstep/certificates/api/render"
@@ -252,88 +249,3 @@ func TestUnimplementedMethods(t *testing.T) {
252249
})
253250
}
254251
}
255-
256-
func TestList_MarshalJSON(t *testing.T) {
257-
258-
k := map[string]any{
259-
"use": "sig",
260-
"kty": "EC",
261-
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
262-
"crv": "P-256",
263-
"alg": "ES256",
264-
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
265-
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
266-
}
267-
key := squarejose.JSONWebKey{}
268-
b, err := json.Marshal(k)
269-
assert.FatalError(t, err)
270-
err = json.Unmarshal(b, &key)
271-
assert.FatalError(t, err)
272-
273-
l := List{
274-
&SCEP{
275-
Name: "scep",
276-
Type: "scep",
277-
ChallengePassword: "not-so-secret",
278-
MinimumPublicKeyLength: 2048,
279-
EncryptionAlgorithmIdentifier: 0,
280-
},
281-
&JWK{
282-
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
283-
Key: &key,
284-
Name: "step-cli",
285-
Type: "JWK",
286-
},
287-
}
288-
289-
expected := []map[string]any{
290-
{
291-
"type": "scep",
292-
"name": "scep",
293-
"challenge": "*** REDACTED ***",
294-
"minimumPublicKeyLength": 2048,
295-
},
296-
{
297-
"type": "JWK",
298-
"name": "step-cli",
299-
"key": map[string]any{
300-
"use": "sig",
301-
"kty": "EC",
302-
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
303-
"crv": "P-256",
304-
"alg": "ES256",
305-
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
306-
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
307-
},
308-
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
309-
},
310-
}
311-
312-
expBytes, err := json.Marshal(expected)
313-
sassert.NoError(t, err)
314-
315-
bl, err := l.MarshalJSON()
316-
sassert.NoError(t, err)
317-
sassert.JSONEq(t, string(expBytes), string(bl))
318-
319-
keyCopy := key
320-
expList := List{
321-
&SCEP{
322-
Name: "scep",
323-
Type: "scep",
324-
ChallengePassword: "not-so-secret",
325-
MinimumPublicKeyLength: 2048,
326-
EncryptionAlgorithmIdentifier: 0,
327-
},
328-
&JWK{
329-
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
330-
Key: &keyCopy,
331-
Name: "step-cli",
332-
Type: "JWK",
333-
},
334-
}
335-
336-
// MarshalJSON must not affect the struct properties itself
337-
sassert.Equal(t, expList, l)
338-
339-
}

0 commit comments

Comments
 (0)