Skip to content

Commit

Permalink
feat(ssh): add ssh key management API
Browse files Browse the repository at this point in the history
support management ssh key via API
  • Loading branch information
orangedeng committed Dec 19, 2022
1 parent 45b618b commit 4ca8335
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 23 deletions.
8 changes: 5 additions & 3 deletions cmd/sshkey/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ func validateCreateArgs(cmd *cobra.Command, args []string) (err error) {

func create(cmd *cobra.Command, args []string) error {
toSave := common.SSHKey{
Name: args[0],
Name: args[0],
SSHPassphrase: sshKeyFlags.Passphrase,
Bits: sshKeyFlags.Bits,
}

if sshKeyFlags.Generate {
Expand All @@ -165,7 +167,7 @@ func create(cmd *cobra.Command, args []string) error {
}
cmd.Printf(infoMsg + "...\n")

if err := pkgsshkey.GenerateSSHKey(&toSave, sshKeyFlags.Passphrase, sshKeyFlags.Bits); err != nil {
if err := pkgsshkey.GenerateSSHKey(&toSave); err != nil {
return err
}

Expand Down Expand Up @@ -195,7 +197,7 @@ func create(cmd *cobra.Command, args []string) error {
}
*pointer = string(content)
}
if err := pkgsshkey.CreateSSHKey(&toSave, sshKeyFlags.Passphrase); err != nil {
if err := pkgsshkey.CreateSSHKey(&toSave); err != nil {
return err
}
cmd.Printf("ssh key %s loaded\n", toSave.Name)
Expand Down
17 changes: 10 additions & 7 deletions pkg/common/sshkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ import (
)

type SSHKey struct {
Name string `json:"name" gorm:"primaryKey;not null"`
GenerateKey bool `json:"generate-key,omitempty" gorm:"-:all" norman:"writeOnly,noupdate"`
HasPassword bool `json:"has-password,omitempty" norman:"readonly"`
Name string `json:"name" gorm:"primaryKey;not null" wrangler:"required"`
GenerateKey bool `json:"generate-key,omitempty" gorm:"-:all" wrangler:"writeOnly,noupdate"`
HasPassword bool `json:"has-password" wrangler:"nocreate,noupdate"`

SSHCert string `json:"ssh-cert,omitempty" yaml:"ssh-cert,omitempty"`
SSHKey string `json:"ssh-key,omitempty" yaml:"ssh-key,omitempty" norman:"type=password"`
SSHPublicKey string `json:"ssh-key-public,omitempty" yaml:"ssh-key-public,omitempty"`
SSHPassphrase string `json:"ssh-passphrase,omitempty" gorm:"-:all" wrangler:"type=password,nullable"`
Bits int `json:"bits,omitempty" gorm:"-:all" wrangler:"default=2048,nullable"`

SSHCert string `json:"ssh-cert,omitempty" yaml:"ssh-cert,omitempty" wrangler:"nullable"`
SSHKey string `json:"ssh-key,omitempty" yaml:"ssh-key,omitempty" wrangler:"writeOnly,nullable"`
SSHPublicKey string `json:"ssh-key-public,omitempty" yaml:"ssh-key-public,omitempty" wrangler:"nullable"`
}

func (s *SSHKey) GetID() string {
func (s SSHKey) GetID() string {
return s.Name
}

Expand Down
14 changes: 14 additions & 0 deletions pkg/server/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/cnrancher/autok3s/pkg/server/store/pkg"
"github.com/cnrancher/autok3s/pkg/server/store/provider"
"github.com/cnrancher/autok3s/pkg/server/store/settings"
"github.com/cnrancher/autok3s/pkg/server/store/sshkey"
"github.com/cnrancher/autok3s/pkg/server/store/template"
"github.com/cnrancher/autok3s/pkg/server/store/websocket"
wkube "github.com/cnrancher/autok3s/pkg/server/store/websocket/kubectl"
Expand Down Expand Up @@ -133,3 +134,16 @@ func initPackage(s *types.APISchemas) {
schema.LinkHandlers = pkg.LinkHandlers()
})
}

func initSSHKey(s *types.APISchemas) {
s.MustImportAndCustomize(common.SSHKey{}, func(schema *types.APISchema) {
schema.CollectionMethods = []string{http.MethodGet, http.MethodPost}
schema.ResourceMethods = []string{http.MethodGet, http.MethodDelete}
schema.Store = &sshkey.Store{}
schema.ActionHandlers = sshkey.ActionHandlers()
schema.Formatter = sshkey.Format
schema.ResourceActions["export"] = wranglertypes.Action{
Output: "sshkey",
}
})
}
1 change: 1 addition & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func Start() http.Handler {
initExplorer(s.Schemas)
initSettings(s.Schemas)
initPackage(s.Schemas)
initSSHKey(s.Schemas)

apiroot.Register(s.Schemas, []string{"v1"})
router := mux.NewRouter()
Expand Down
35 changes: 35 additions & 0 deletions pkg/server/store/sshkey/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package sshkey

import (
"net/http"

"github.com/cnrancher/autok3s/pkg/common"

"github.com/rancher/apiserver/pkg/apierror"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/wrangler/pkg/schemas/validation"
)

func ActionHandlers() map[string]http.Handler {
return map[string]http.Handler{
"export": http.HandlerFunc(exportHandler),
}
}

func exportHandler(w http.ResponseWriter, r *http.Request) {
apiContext := types.GetAPIContext(r.Context())
defer r.Body.Close()
name := apiContext.Name
sshkeys, err := common.DefaultDB.ListSSHKey(&name)
if err != nil {
apiContext.WriteError(apierror.WrapAPIError(err, validation.ServerError, "failed to get sshkey"))
return
}
obj := common.GetAPIObject(*sshkeys[0])
apiContext.WriteResponse(200, *obj)
return
}

func Format(request *types.APIRequest, resource *types.RawResource) {
resource.AddAction(request, "export")
}
90 changes: 90 additions & 0 deletions pkg/server/store/sshkey/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package sshkey

import (
"fmt"

"github.com/cnrancher/autok3s/pkg/common"
pkgsshkey "github.com/cnrancher/autok3s/pkg/sshkey"

"github.com/rancher/apiserver/pkg/apierror"
"github.com/rancher/apiserver/pkg/store/empty"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/wrangler/pkg/data/convert"
"github.com/rancher/wrangler/pkg/schemas/validation"
)

type Store struct {
empty.Store
}

func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) {
rtn := common.SSHKey{}
if err := convert.ToObj(data.Object, &rtn); err != nil {
return types.APIObject{}, err
}
if exist, _ := common.DefaultDB.SSHKeyExists(rtn.Name); exist {
return types.APIObject{}, apierror.NewAPIError(validation.Conflict, fmt.Sprintf("sshkey %s already exists", rtn.Name))
}

if rtn.GenerateKey {
if rtn.Bits%256 != 0 {
return types.APIObject{}, apierror.NewFieldAPIError(validation.InvalidBodyContent, "bits", "ssh private key bit size should be a multiple of 256")
}
if err := pkgsshkey.GenerateSSHKey(&rtn); err != nil {
return types.APIObject{}, apierror.WrapAPIError(err, validation.ServerError, "failed to generate ssh key")
}
// remove passphrase for response
rtn.SSHPassphrase = ""
} else {
if rtn.SSHKey == "" {
return types.APIObject{}, apierror.NewAPIError(validation.MissingRequired, "ssh private key is required when not generating new ssh key pair")
}
if ok, err := pkgsshkey.NeedPasswordRaw([]byte(rtn.SSHKey)); err != nil {
return types.APIObject{}, apierror.WrapAPIError(err, validation.InvalidBodyContent, "failed to parse ssh private key")
} else if ok && rtn.SSHPassphrase == "" {
return types.APIObject{}, apierror.NewAPIError(validation.MissingRequired, "ssh key passphrase is required when the private is encrypted")
}
if err := pkgsshkey.CreateSSHKey(&rtn); err != nil {
return types.APIObject{}, apierror.WrapAPIError(err, validation.ServerError, "failed to save ssh key")
}
}

return *common.GetAPIObject(rtn), nil
}

func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
var rtn types.APIObjectList
sshkeys, err := common.DefaultDB.ListSSHKey(nil)
if err != nil {
return rtn, err
}
for _, key := range sshkeys {
key.SSHKey = ""
obj := common.GetAPIObject(*key)
rtn.Objects = append(rtn.Objects, *obj)
}
return rtn, nil
}

func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
rtn, err := s.ByID(apiOp, schema, id)
if err != nil {
return types.APIObject{}, err
}

return rtn, common.DefaultDB.DeleteSSHKey(id)
}

func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
rtn, err := common.DefaultDB.ListSSHKey(&id)
if err != nil {
return types.APIObject{}, err
}
rtn[0].SSHKey = ""
obj := common.GetAPIObject(rtn[0])
return *obj, nil
}

func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) {
return common.DefaultDB.Watch(apiOp, schema), nil
}
29 changes: 16 additions & 13 deletions pkg/sshkey/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ import (
"golang.org/x/crypto/ssh"
)

func NeedPassword(keypath string) (bool, error) {
content, err := utils.GetFileContent(keypath)
if err != nil {
return false, err
}

func NeedPasswordRaw(content []byte) (bool, error) {
if _, err := ssh.ParseRawPrivateKey(content); err != nil {
if _, ok := err.(*ssh.PassphraseMissingError); ok {
return true, nil
Expand All @@ -29,12 +24,20 @@ func NeedPassword(keypath string) (bool, error) {
return false, nil
}

func CreateSSHKey(key *common.SSHKey, passphrase string) error {
func NeedPassword(keypath string) (bool, error) {
content, err := utils.GetFileContent(keypath)
if err != nil {
return false, err
}
return NeedPasswordRaw(content)
}

func CreateSSHKey(key *common.SSHKey) error {
var err error
var publicKey ssh.PublicKey
var privateKey ssh.Signer
if passphrase != "" {
privateKey, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key.SSHKey), []byte(passphrase))
if key.SSHPassphrase != "" {
privateKey, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key.SSHKey), []byte(key.SSHPassphrase))
key.HasPassword = true
} else {
privateKey, err = ssh.ParsePrivateKey([]byte(key.SSHKey))
Expand Down Expand Up @@ -66,8 +69,8 @@ func CreateSSHKey(key *common.SSHKey, passphrase string) error {
return common.DefaultDB.SaveSSHKey(*key)
}

func GenerateSSHKey(key *common.SSHKey, passphrase string, bits int) error {
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
func GenerateSSHKey(key *common.SSHKey) error {
privateKey, err := rsa.GenerateKey(rand.Reader, key.Bits)
if err != nil {
return err
}
Expand All @@ -76,8 +79,8 @@ func GenerateSSHKey(key *common.SSHKey, passphrase string, bits int) error {
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}

if passphrase != "" {
block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(passphrase), x509.PEMCipherAES256)
if key.SSHPassphrase != "" {
block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(key.SSHPassphrase), x509.PEMCipherAES256)
if err != nil {
return err
}
Expand Down

0 comments on commit 4ca8335

Please sign in to comment.