GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sshkeyring.go 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package kubernetes
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "os"
  6. "path"
  7. "sync"
  8. "k8s.io/client-go/1.5/kubernetes/typed/core/v1"
  9. "k8s.io/client-go/1.5/pkg/api"
  10. "github.com/weaveworks/flux/ssh"
  11. )
  12. const (
  13. // The private key file must have these permissions, or ssh will refuse to
  14. // use it
  15. privateKeyFileMode = os.FileMode(0400)
  16. )
  17. // SSHKeyRingConfig is used to configure the keyring with key generation
  18. // options and the parameters of its backing kubernetes secret resource.
  19. // SecretVolumeMountPath must be mounted RW for regenerate() to work, and to
  20. // set the privateKeyFileMode on the identity secret file.
  21. type SSHKeyRingConfig struct {
  22. SecretAPI v1.SecretInterface
  23. SecretName string
  24. SecretVolumeMountPath string // e.g. "/etc/fluxd/ssh"
  25. SecretDataKey string // e.g. "identity"
  26. KeyBits ssh.OptionalValue
  27. KeyType ssh.OptionalValue
  28. }
  29. type sshKeyRing struct {
  30. sync.RWMutex
  31. SSHKeyRingConfig
  32. publicKey ssh.PublicKey
  33. privateKeyPath string
  34. }
  35. // NewSSHKeyRing constructs an sshKeyRing backed by a kubernetes secret
  36. // resource. The keyring is initialised with the key that was previously stored
  37. // in the secret (either by regenerate() or an administrator), or a freshly
  38. // generated key if none was found.
  39. func NewSSHKeyRing(config SSHKeyRingConfig) (*sshKeyRing, error) {
  40. skr := &sshKeyRing{SSHKeyRingConfig: config}
  41. privateKeyPath := path.Join(skr.SecretVolumeMountPath, skr.SecretDataKey)
  42. fileInfo, err := os.Stat(privateKeyPath)
  43. switch {
  44. case os.IsNotExist(err):
  45. if err := skr.Regenerate(); err != nil {
  46. return nil, err
  47. }
  48. case err != nil:
  49. return nil, err
  50. case fileInfo.Mode() != privateKeyFileMode:
  51. if err := os.Chmod(privateKeyPath, privateKeyFileMode); err != nil {
  52. return nil, err
  53. }
  54. fallthrough
  55. default:
  56. publicKey, err := ssh.ExtractPublicKey(privateKeyPath)
  57. if err != nil {
  58. return nil, err
  59. }
  60. skr.privateKeyPath = privateKeyPath
  61. skr.publicKey = publicKey
  62. }
  63. return skr, nil
  64. }
  65. // KeyPair returns the current public key and the path to its corresponding
  66. // private key. The private key file is guaranteed to exist for the lifetime of
  67. // the process, however as the returned pair can be discarded from the keyring
  68. // at any time by use of the regenerate() method it is inadvisable to cache the
  69. // results for long periods; instead request the key pair from the ring
  70. // immediately prior to each use.
  71. func (skr *sshKeyRing) KeyPair() (publicKey ssh.PublicKey, privateKeyPath string) {
  72. skr.RLock()
  73. defer skr.RUnlock()
  74. return skr.publicKey, skr.privateKeyPath
  75. }
  76. // regenerate creates a new keypair in the configured SecretVolumeMountPath and
  77. // updates the kubernetes secret resource with the private key so that it will
  78. // be available to the keyring after restart. If this operation is successful
  79. // the keyPair() method will return the new pair; if it fails for any reason,
  80. // keyPair() will continue to return the existing pair.
  81. //
  82. // BUG(awh) Updating the kubernetes secret should be done via an ephemeral
  83. // external executable invoked with coredumps disabled and using
  84. // syscall.Mlockall(MCL_FUTURE) in conjunction with an appropriate ulimit to
  85. // ensure the private key isn't unintentionally written to persistent storage.
  86. func (skr *sshKeyRing) Regenerate() error {
  87. privateKeyPath, privateKey, publicKey, err := ssh.KeyGen(skr.KeyBits, skr.KeyType, skr.SecretVolumeMountPath)
  88. if err != nil {
  89. return err
  90. }
  91. patch := map[string]map[string]string{
  92. "data": map[string]string{
  93. "identity": base64.StdEncoding.EncodeToString(privateKey),
  94. },
  95. }
  96. jsonPatch, err := json.Marshal(patch)
  97. if err != nil {
  98. return err
  99. }
  100. _, err = skr.SecretAPI.Patch(skr.SecretName, api.StrategicMergePatchType, jsonPatch)
  101. if err != nil {
  102. return err
  103. }
  104. skr.Lock()
  105. skr.privateKeyPath = privateKeyPath
  106. skr.publicKey = publicKey
  107. skr.Unlock()
  108. return nil
  109. }