mirror of
https://github.com/outbackdingo/certificates.git
synced 2026-01-27 10:18:34 +00:00
Add support for /ssh/bastion method.
This commit is contained in:
@@ -261,6 +261,7 @@ func (h *caHandler) Route(r Router) {
|
||||
r.MethodFunc("POST", "/ssh/config/{type}", h.SSHConfig)
|
||||
r.MethodFunc("POST", "/ssh/check-host", h.SSHCheckHost)
|
||||
r.MethodFunc("GET", "/ssh/get-hosts", h.SSHGetHosts)
|
||||
r.MethodFunc("POST", "/ssh/bastion", h.SSHBastion)
|
||||
|
||||
// For compatibility with old code:
|
||||
r.MethodFunc("POST", "/re-sign", h.Renew)
|
||||
|
||||
@@ -557,6 +557,7 @@ type mockAuthority struct {
|
||||
getSSHFederation func() (*authority.SSHKeys, error)
|
||||
getSSHConfig func(typ string, data map[string]string) ([]templates.Output, error)
|
||||
checkSSHHost func(principal string) (bool, error)
|
||||
getSSHBastion func(user string, hostname string) (*authority.Bastion, error)
|
||||
}
|
||||
|
||||
// TODO: remove once Authorize is deprecated.
|
||||
@@ -711,6 +712,13 @@ func (m *mockAuthority) CheckSSHHost(principal string) (bool, error) {
|
||||
return m.ret1.(bool), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) GetSSHBastion(user string, hostname string) (*authority.Bastion, error) {
|
||||
if m.getSSHBastion != nil {
|
||||
return m.getSSHBastion(user, hostname)
|
||||
}
|
||||
return m.ret1.(*authority.Bastion), m.err
|
||||
}
|
||||
|
||||
func Test_caHandler_Route(t *testing.T) {
|
||||
type fields struct {
|
||||
Authority Authority
|
||||
|
||||
47
api/ssh.go
47
api/ssh.go
@@ -24,6 +24,7 @@ type SSHAuthority interface {
|
||||
GetSSHConfig(typ string, data map[string]string) ([]templates.Output, error)
|
||||
CheckSSHHost(principal string) (bool, error)
|
||||
GetSSHHosts() ([]string, error)
|
||||
GetSSHBastion(user string, hostname string) (*authority.Bastion, error)
|
||||
}
|
||||
|
||||
// SSHSignRequest is the request body of an SSH certificate request.
|
||||
@@ -207,6 +208,28 @@ type SSHCheckPrincipalResponse struct {
|
||||
Exists bool `json:"exists"`
|
||||
}
|
||||
|
||||
// SSHBastionRequest is the request body used to get the bastion for a given
|
||||
// host.
|
||||
type SSHBastionRequest struct {
|
||||
User string `json:"user"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
// Validate checks the values of the SSHBastionRequest.
|
||||
func (r *SSHBastionRequest) Validate() error {
|
||||
if r.Hostname == "" {
|
||||
return errors.New("missing or empty hostname")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SSHBastionResponse is the response body used to return the bastion for a
|
||||
// given host.
|
||||
type SSHBastionResponse struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Bastion *authority.Bastion `json:"bastion,omitempty"`
|
||||
}
|
||||
|
||||
// SSHSign 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.
|
||||
@@ -392,3 +415,27 @@ func (h *caHandler) SSHGetHosts(w http.ResponseWriter, r *http.Request) {
|
||||
Hosts: hosts,
|
||||
})
|
||||
}
|
||||
|
||||
// SSHBastion provides returns the bastion configured if any.
|
||||
func (h *caHandler) SSHBastion(w http.ResponseWriter, r *http.Request) {
|
||||
var body SSHBastionRequest
|
||||
if err := ReadJSON(r.Body, &body); err != nil {
|
||||
WriteError(w, BadRequest(errors.Wrap(err, "error reading request body")))
|
||||
return
|
||||
}
|
||||
if err := body.Validate(); err != nil {
|
||||
WriteError(w, BadRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
bastion, err := h.Authority.GetSSHBastion(body.User, body.Hostname)
|
||||
if err != nil {
|
||||
WriteError(w, InternalServerError(err))
|
||||
return
|
||||
}
|
||||
|
||||
JSON(w, &SSHBastionResponse{
|
||||
Hostname: body.Hostname,
|
||||
Bastion: bastion,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -537,6 +537,61 @@ func Test_caHandler_SSHCheckHost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_caHandler_SSHBastion(t *testing.T) {
|
||||
bastion := &authority.Bastion{
|
||||
Hostname: "bastion.local",
|
||||
}
|
||||
bastionPort := &authority.Bastion{
|
||||
Hostname: "bastion.local",
|
||||
Port: "2222",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
bastion *authority.Bastion
|
||||
bastionErr error
|
||||
req []byte
|
||||
body []byte
|
||||
statusCode int
|
||||
}{
|
||||
{"ok", bastion, nil, []byte(`{"hostname":"host.local"}`), []byte(`{"hostname":"host.local","bastion":{"hostname":"bastion.local"}}`), http.StatusOK},
|
||||
{"ok", bastionPort, nil, []byte(`{"hostname":"host.local","user":"user"}`), []byte(`{"hostname":"host.local","bastion":{"hostname":"bastion.local","port":"2222"}}`), http.StatusOK},
|
||||
{"empty", nil, nil, []byte(`{"hostname":"host.local"}`), []byte(`{"hostname":"host.local"}`), http.StatusOK},
|
||||
{"bad json", bastion, nil, []byte(`bad json`), nil, http.StatusBadRequest},
|
||||
{"bad request", bastion, nil, []byte(`{"hostname": ""}`), nil, http.StatusBadRequest},
|
||||
{"error", nil, fmt.Errorf("an error"), []byte(`{"hostname":"host.local"}`), nil, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := New(&mockAuthority{
|
||||
getSSHBastion: func(user, hostname string) (*authority.Bastion, error) {
|
||||
return tt.bastion, tt.bastionErr
|
||||
},
|
||||
}).(*caHandler)
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com/ssh/bastion", bytes.NewReader(tt.req))
|
||||
w := httptest.NewRecorder()
|
||||
h.SSHBastion(logging.NewResponseLogger(w), req)
|
||||
res := w.Result()
|
||||
|
||||
if res.StatusCode != tt.statusCode {
|
||||
t.Errorf("caHandler.SSHBastion StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
t.Errorf("caHandler.SSHBastion unexpected error = %v", err)
|
||||
}
|
||||
if tt.statusCode < http.StatusBadRequest {
|
||||
if !bytes.Equal(bytes.TrimSpace(body), tt.body) {
|
||||
t.Errorf("caHandler.SSHBastion Body = %s, wants %s", body, tt.body)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHPublicKey_MarshalJSON(t *testing.T) {
|
||||
key, err := ssh.NewPublicKey(sshUserKey.Public())
|
||||
assert.FatalError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user