Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/client/ca_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type ExportedAuthority struct {
// Data is the output of the exported authority.
// May be an SSH authorized key, an SSH known hosts entry, a DER or a PEM,
// depending on the type of the exported authority.
Data []byte
Data []byte `json:"data"`
}

// ExportAllAuthorities exports public keys of all authorities of a particular
Expand Down
39 changes: 31 additions & 8 deletions lib/web/ca_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package web
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
Expand Down Expand Up @@ -52,12 +53,6 @@ func (h *Handler) authExportPublicError(w http.ResponseWriter, r *http.Request,

query := r.URL.Query()
caType := query.Get("type") // validated by ExportAllAuthorities
format := query.Get("format")

const formatZip = "zip"
if format != "" && format != formatZip {
return trace.BadParameter("unsupported format %q", format)
}

ctx := r.Context()
authorities, err := client.ExportAllAuthorities(
Expand All @@ -72,11 +67,23 @@ func (h *Handler) authExportPublicError(w http.ResponseWriter, r *http.Request,
return trace.Wrap(err)
}

if format == formatZip {
format := query.Get("format")

const formatZip = "zip"
const formatJSON = "json"
switch format {
case "":
break
case formatZip:
return h.authExportPublicZip(w, r, authorities)
case formatJSON:
return h.authExportPublicJSON(w, r, authorities)
default:
return trace.BadParameter("unsupported format %q", format)
}

if l := len(authorities); l > 1 {
return trace.BadParameter("found %d authorities to export, use format=%s to export all", l, formatZip)
return trace.BadParameter("found %d authorities to export, use format=%s or format=%s to export all", l, formatZip, formatJSON)
}

// ServeContent sets the correct headers: Content-Type, Content-Length and Accept-Ranges.
Expand Down Expand Up @@ -119,3 +126,19 @@ func (h *Handler) authExportPublicZip(
http.ServeContent(w, r, zipName, now, bytes.NewReader(out.Bytes()))
return nil
}

func (h *Handler) authExportPublicJSON(
w http.ResponseWriter,
r *http.Request,
authorities []*client.ExportedAuthority,
) error {
marshalledAuthorities, err := json.Marshal(authorities)
if err != nil {
return trace.Wrap(err, "failed to JSON marshal authorities")
}

// File name is not critical here. It is only used by `ServeContent` to determine the value of the
// `Content-Type` header.
http.ServeContent(w, r, "export.json", time.Now(), bytes.NewReader(marshalledAuthorities))
Comment thread
zmb3 marked this conversation as resolved.
return nil
}
30 changes: 30 additions & 0 deletions lib/web/ca_export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io"
Expand All @@ -33,6 +34,8 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/lib/client"
)

func TestAuthExport(t *testing.T) {
Expand Down Expand Up @@ -90,6 +93,22 @@ func TestAuthExport(t *testing.T) {
validateFormatZip(t, body, wantCAFiles, validateTLSCertificatePEMFunc)
}

validateFormatJSON := func(
t *testing.T,
body []byte,
wantCAFiles int,
validateCAFile func(t *testing.T, contents []byte),
) {
var authorities []client.ExportedAuthority
err := json.Unmarshal(body, &authorities)
require.NoError(t, err)
assert.Len(t, authorities, wantCAFiles)

for _, authority := range authorities {
validateCAFile(t, authority.Data)
}
}

ctx := context.Background()

for _, tt := range []struct {
Expand Down Expand Up @@ -215,6 +234,17 @@ func TestAuthExport(t *testing.T) {
validateFormatZipPEM(t, b, 1 /* wantCAFiles */)
},
},
{
name: "format=json",
params: url.Values{
"type": []string{"db-client"},
"format": []string{"json"},
},
expectedStatus: http.StatusOK,
assertBody: func(t *testing.T, b []byte) {
validateFormatJSON(t, b, 1, validateTLSCertificatePEMFunc)
},
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
Expand Down