-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move SSH keys handling to the kernel
- Loading branch information
1 parent
33c3a10
commit 5059605
Showing
7 changed files
with
255 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package router | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/vertex-center/vertex/services" | ||
"github.com/vertex-center/vertex/types" | ||
) | ||
|
||
func addSecurityKernelRoutes(r *gin.RouterGroup) { | ||
r.GET("/ssh", handleGetSSHKeyKernel) | ||
r.POST("/ssh", handleAddSSHKeyKernel) | ||
r.DELETE("/ssh/:fingerprint", handleDeleteSSHKeyKernel) | ||
} | ||
|
||
// handleGetSSHKey handles the retrieval of the SSH key. | ||
// Errors can be: | ||
// - failed_to_get_ssh_keys: failed to get the SSH keys. | ||
func handleGetSSHKeyKernel(c *gin.Context) { | ||
keys, err := sshKernelService.GetAll() | ||
if err != nil { | ||
_ = c.AbortWithError(http.StatusInternalServerError, types.APIError{ | ||
Code: "failed_to_get_ssh_keys", | ||
Message: fmt.Sprintf("failed to get SSH keys: %v", err), | ||
}) | ||
return | ||
} | ||
|
||
c.JSON(http.StatusOK, keys) | ||
} | ||
|
||
// handleAddSSHKey handles the addition of an SSH key. | ||
// Errors can be: | ||
// - failed_to_parse_body: failed to parse the request body. | ||
// - failed_to_add_ssh_key: failed to add the SSH key. | ||
func handleAddSSHKeyKernel(c *gin.Context) { | ||
buf := new(bytes.Buffer) | ||
_, err := buf.ReadFrom(c.Request.Body) | ||
if err != nil { | ||
_ = c.AbortWithError(http.StatusBadRequest, types.APIError{ | ||
Code: "failed_to_parse_body", | ||
Message: fmt.Sprintf("failed to parse request body: %v", err), | ||
}) | ||
return | ||
} | ||
key := buf.String() | ||
|
||
err = sshKernelService.Add(key) | ||
if err != nil && errors.Is(err, services.ErrInvalidPublicKey) { | ||
_ = c.AbortWithError(http.StatusBadRequest, types.APIError{ | ||
Code: "invalid_public_key", | ||
Message: fmt.Sprintf("error while parsing the public key: %v", err), | ||
}) | ||
return | ||
} else if err != nil { | ||
_ = c.AbortWithError(http.StatusInternalServerError, types.APIError{ | ||
Code: "failed_to_add_ssh_key", | ||
Message: fmt.Sprintf("failed to add SSH key: %v", err), | ||
}) | ||
return | ||
} | ||
|
||
c.Status(http.StatusCreated) | ||
} | ||
|
||
// handleDeleteSSHKey handles the deletion of an SSH key. | ||
// Errors can be: | ||
// - failed_to_parse_body: failed to parse the request body. | ||
// - failed_to_delete_ssh_key: failed to delete the SSH key. | ||
func handleDeleteSSHKeyKernel(c *gin.Context) { | ||
fingerprint := c.Param("fingerprint") | ||
if fingerprint == "" { | ||
_ = c.AbortWithError(http.StatusBadRequest, types.APIError{ | ||
Code: "invalid_fingerprint", | ||
Message: "invalid fingerprint", | ||
}) | ||
return | ||
} | ||
|
||
err := sshKernelService.Delete(fingerprint) | ||
if err != nil { | ||
_ = c.AbortWithError(http.StatusInternalServerError, types.APIError{ | ||
Code: "failed_to_delete_ssh_key", | ||
Message: fmt.Sprintf("failed to delete SSH key: %v", err), | ||
}) | ||
return | ||
} | ||
|
||
c.Status(http.StatusNoContent) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,124 +1,39 @@ | ||
package services | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"path" | ||
"strings" | ||
"context" | ||
|
||
"github.com/vertex-center/vertex/pkg/log" | ||
"github.com/carlmjohnson/requests" | ||
"github.com/vertex-center/vertex/types" | ||
"golang.org/x/crypto/ssh" | ||
) | ||
|
||
var ( | ||
ErrInvalidPublicKey = errors.New("invalid key") | ||
) | ||
|
||
type SSHService struct { | ||
authorizedKeysPath string | ||
} | ||
|
||
type SSHServiceParams struct { | ||
AuthorizedKeysPath string | ||
} | ||
|
||
func NewSSHService(params *SSHServiceParams) SSHService { | ||
func NewSSHService() SSHService { | ||
s := SSHService{} | ||
|
||
if params == nil { | ||
params = &SSHServiceParams{} | ||
} | ||
|
||
s.authorizedKeysPath = params.AuthorizedKeysPath | ||
if s.authorizedKeysPath == "" { | ||
var err error | ||
s.authorizedKeysPath, err = getAuthorizedKeysPath() | ||
if err != nil { | ||
log.Error(err) | ||
} | ||
} | ||
|
||
return s | ||
} | ||
|
||
func (s *SSHService) GetAll() ([]types.PublicKey, error) { | ||
bytes, err := os.ReadFile(s.authorizedKeysPath) | ||
if err != nil && errors.Is(err, os.ErrNotExist) { | ||
log.Info("authorized_keys file does not exist") | ||
return []types.PublicKey{}, nil | ||
} else if err != nil { | ||
return nil, err | ||
} | ||
|
||
var publicKeys []ssh.PublicKey | ||
for len(bytes) > 0 { | ||
pubKey, _, _, rest, _ := ssh.ParseAuthorizedKey(bytes) | ||
if pubKey != nil { | ||
publicKeys = append(publicKeys, pubKey) | ||
} | ||
bytes = rest | ||
} | ||
|
||
keys := []types.PublicKey{} | ||
for _, key := range publicKeys { | ||
keys = append(keys, types.PublicKey{ | ||
Type: key.Type(), | ||
FingerprintSHA256: ssh.FingerprintSHA256(key), | ||
}) | ||
} | ||
|
||
return keys, nil | ||
var keys []types.PublicKey | ||
err := requests.URL("http://localhost:6131/api/security/ssh"). | ||
ToJSON(&keys). | ||
Fetch(context.Background()) | ||
return keys, err | ||
} | ||
|
||
// Add adds an SSH key to the authorized keys file. The key must | ||
// be a valid SSH public key, otherwise ErrInvalidPublicKey is returned. | ||
func (s *SSHService) Add(authorizedKey string) error { | ||
// Check if the key is valid. | ||
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(authorizedKey)) | ||
if err != nil { | ||
return ErrInvalidPublicKey | ||
} | ||
|
||
file, err := os.OpenFile(s.authorizedKeysPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
_, err = file.WriteString(authorizedKey + "\n") | ||
return err | ||
func (s *SSHService) Add(key string) error { | ||
return requests.URL("http://localhost:6131/api/security/ssh"). | ||
Post(). | ||
BodyBytes([]byte(key)). | ||
Fetch(context.Background()) | ||
} | ||
|
||
// Delete deletes an SSH key from the authorized keys file. | ||
func (s *SSHService) Delete(fingerprint string) error { | ||
content, err := os.ReadFile(s.authorizedKeysPath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
lines := strings.Split(string(content), "\n") | ||
for i, line := range lines { | ||
key, _, _, _, _ := ssh.ParseAuthorizedKey([]byte(line)) | ||
if key == nil { | ||
continue | ||
} | ||
|
||
fingerprintLine := ssh.FingerprintSHA256(key) | ||
|
||
if fingerprintLine == fingerprint { | ||
lines = append(lines[:i], lines[i+1:]...) | ||
break | ||
} | ||
} | ||
|
||
return os.WriteFile(s.authorizedKeysPath, []byte(strings.Join(lines, "\n")), 0644) | ||
} | ||
|
||
func getAuthorizedKeysPath() (string, error) { | ||
dir, err := os.UserHomeDir() | ||
if err != nil { | ||
return "", err | ||
} | ||
return path.Join(dir, ".ssh", "authorized_keys"), nil | ||
return requests.URL("http://localhost:6131/"). | ||
Pathf("/api/security/ssh/%s", fingerprint). | ||
Delete(). | ||
Fetch(context.Background()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.