mirror of
https://github.com/semaphoreui/semaphore.git
synced 2024-11-23 12:30:41 +01:00
213 lines
4.4 KiB
Go
213 lines
4.4 KiB
Go
package db
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
|
)
|
|
|
|
const (
|
|
AccessKeySSH = "ssh"
|
|
AccessKeyNone = "none"
|
|
AccessKeyLoginPassword = "login_password"
|
|
)
|
|
|
|
// AccessKey represents a key used to access a machine with ansible from semaphore
|
|
type AccessKey struct {
|
|
ID int `db:"id" json:"id"`
|
|
Name string `db:"name" json:"name" binding:"required"`
|
|
// 'ssh/login_password/none'
|
|
Type string `db:"type" json:"type" binding:"required"`
|
|
|
|
ProjectID *int `db:"project_id" json:"project_id"`
|
|
|
|
// Secret used internally, do not assign this field.
|
|
// You should use methods SerializeSecret to fill this field.
|
|
Secret *string `db:"secret" json:"-"`
|
|
|
|
Removed bool `db:"removed" json:"removed"`
|
|
|
|
LoginPassword LoginPassword `db:"-" json:"login_password"`
|
|
SshKey SshKey `db:"-" json:"ssh"`
|
|
OverrideSecret bool `db:"-" json:"override_secret"`
|
|
}
|
|
|
|
type LoginPassword struct {
|
|
Login string `json:"login"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type SshKey struct {
|
|
Passphrase string `json:"passphrase"`
|
|
PrivateKey string `json:"private_key"`
|
|
}
|
|
|
|
// GetPath returns the location of the access key once written to disk
|
|
func (key AccessKey) GetPath() string {
|
|
return util.Config.TmpPath + "/access_key_" + strconv.Itoa(key.ID)
|
|
}
|
|
|
|
func (key AccessKey) Validate(validateSecretFields bool) error {
|
|
if key.Name == "" {
|
|
return fmt.Errorf("name can not be empty")
|
|
}
|
|
|
|
if !validateSecretFields {
|
|
return nil
|
|
}
|
|
|
|
switch key.Type {
|
|
case AccessKeySSH:
|
|
if key.SshKey.PrivateKey == "" {
|
|
return fmt.Errorf("private key can not be empty")
|
|
}
|
|
case AccessKeyLoginPassword:
|
|
if key.SshKey.PrivateKey == "" {
|
|
return fmt.Errorf("password can not be empty")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (key *AccessKey) SerializeSecret() error {
|
|
var plaintext []byte
|
|
var err error
|
|
|
|
switch key.Type {
|
|
case AccessKeySSH:
|
|
plaintext, err = json.Marshal(key.SshKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case AccessKeyLoginPassword:
|
|
plaintext, err = json.Marshal(key.LoginPassword)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
key.Secret = nil
|
|
return nil
|
|
}
|
|
|
|
if util.Config.CookieEncryption == "" {
|
|
secret := base64.StdEncoding.EncodeToString(plaintext)
|
|
key.Secret = &secret
|
|
return nil
|
|
}
|
|
|
|
encryption, err := base64.StdEncoding.DecodeString(util.Config.CookieEncryption)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c, err := aes.NewCipher(encryption)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return err
|
|
}
|
|
|
|
secret := base64.StdEncoding.EncodeToString(gcm.Seal(nonce, nonce, plaintext, nil))
|
|
key.Secret = &secret
|
|
|
|
return nil
|
|
}
|
|
|
|
func (key *AccessKey) unmarshalAppropriateField(secret []byte) (err error) {
|
|
switch key.Type {
|
|
case AccessKeySSH:
|
|
sshKey := SshKey{}
|
|
err = json.Unmarshal(secret, &sshKey)
|
|
if err == nil {
|
|
key.SshKey = sshKey
|
|
}
|
|
case AccessKeyLoginPassword:
|
|
loginPass := LoginPassword{}
|
|
err = json.Unmarshal(secret, &loginPass)
|
|
if err == nil {
|
|
key.LoginPassword = loginPass
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (key *AccessKey) ResetSecret() {
|
|
key.Secret = nil
|
|
key.LoginPassword = LoginPassword{}
|
|
key.SshKey = SshKey{}
|
|
}
|
|
|
|
func (key *AccessKey) DeserializeSecret() error {
|
|
if key.Secret == nil || *key.Secret == "" {
|
|
return nil
|
|
}
|
|
|
|
ciphertext := []byte(*key.Secret)
|
|
|
|
if ciphertext[len(*key.Secret)-1] == '\n' { // not encrypted private key, used for back compatibility
|
|
if key.Type != AccessKeySSH {
|
|
return fmt.Errorf("invalid access key type")
|
|
}
|
|
key.SshKey = SshKey{
|
|
PrivateKey: *key.Secret,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
ciphertext, err := base64.StdEncoding.DecodeString(*key.Secret)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if util.Config.AccessKeyEncryption == "" {
|
|
return key.unmarshalAppropriateField(ciphertext)
|
|
}
|
|
|
|
encryption, err := base64.StdEncoding.DecodeString(util.Config.CookieEncryption)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c, err := aes.NewCipher(encryption)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gcm, err := cipher.NewGCM(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nonceSize := gcm.NonceSize()
|
|
if len(ciphertext) < nonceSize {
|
|
return fmt.Errorf("ciphertext too short")
|
|
}
|
|
|
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
|
|
|
ciphertext, err = gcm.Open(nil, nonce, ciphertext, nil)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return key.unmarshalAppropriateField(ciphertext)
|
|
}
|