Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssh-agent with google cloud kms #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ go 1.12
require (
cloud.google.com/go v0.39.0
github.com/atotto/cloudkms v0.1.2
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 // indirect
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5
)
1 change: 1 addition & 0 deletions kssh-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/kssh-agent
110 changes: 110 additions & 0 deletions kssh-agent/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package main

import (
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"

kms "cloud.google.com/go/kms/apiv1"
"github.com/atotto/cloudkms"
"github.com/atotto/kssh/kssh-agent/server"
"golang.org/x/crypto/ssh/agent"
)

// You can set key path `export KSSH_KEY_PATH=projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]/cryptoKeyVersions/[VERSION]` EC_SIGN_P256_SHA256 Algorithm
var kmsKeyPath = os.Getenv("KSSH_KEY_PATH")

var (
key = flag.String("i", kmsKeyPath, "Selects a Google Cloud KMS resource ID.")
)

const script = `SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;
SSH_AGENT_PID=%d; export SSH_AGENT_PID;
echo Agent pid %d;
`

func main() {
flag.Parse()

if *key == "" {
fmt.Println("Please set kms key")
os.Exit(2)
}

sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
ctx, cancel := context.WithCancel(context.Background())

client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
log.Printf("google cloud kms: %s", err)
os.Exit(1)
}
defer client.Close()

signer, err := cloudkms.NewSigner(client, *key)
if err != nil {
log.Printf("load key: %s", err)
os.Exit(1)
}

kr := agent.NewKeyring()

kr.Add(agent.AddedKey{
PrivateKey: signer,
Comment: "kssh",
})

tmpDir, err := ioutil.TempDir("", "kssh-")
if err != nil {
log.Fatalf("dir: %s", err)
}
pid := os.Getpid()
path := filepath.Join(tmpDir, fmt.Sprintf("agent.%d", pid))

listener, err := net.Listen("unix", path)
if err != nil {
log.Fatalf("listen: %s", err)
}

go server.Serve(listener, kr)

args := os.Args
if len(args) >= 2 {
name := args[1]
args = args[2:]
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
env := os.Environ()
env = append(env, fmt.Sprintf("SSH_AUTH_SOCK=%s", path), fmt.Sprintf("SSH_AGENT_PID=%d", pid))
cmd.Env = env

if err := cmd.Start(); err != nil {
log.Fatalf("exec command: %s", err)
}

go func() {
cmd.Wait()
cancel()
}()
} else {
// print script
fmt.Fprintf(os.Stdout, script, path, pid, pid)
}

select {
case <-sig:
cancel()
case <-ctx.Done():
}
}
28 changes: 28 additions & 0 deletions kssh-agent/server/agent_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package server

import (
"fmt"
"io"
"log"
"net"

"golang.org/x/crypto/ssh/agent"
)

func Serve(listener net.Listener, kr agent.Agent) (env []string, err error) {
for {
conn, err := listener.Accept()
if err != nil {
return nil, fmt.Errorf("accept: %s", err)
}
go func() {
if err := agent.ServeAgent(kr, conn); err != nil {
if err != io.EOF {
log.Printf("serve agent: %s", err)
}
return
}
conn.Close()
}()
}
}
43 changes: 8 additions & 35 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@ package main

import (
"context"
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/signal"
Expand Down Expand Up @@ -52,7 +47,14 @@ func main() {
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
ctx, cancel := context.WithCancel(context.Background())

signer, err := loadSigner(ctx, *key)
client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
log.Printf("google cloud kms: %s", err)
os.Exit(1)
}
defer client.Close()

signer, err := cloudkms.NewSigner(client, *key)
if err != nil {
log.Printf("load key: %s", err)
os.Exit(1)
Expand Down Expand Up @@ -168,32 +170,3 @@ func run(ctx context.Context, signer ssh.Signer, user, host string, port int) er
}
return nil
}

func loadSigner(ctx context.Context, path string) (crypto.Signer, error) {
if strings.HasPrefix(path, "projects") {
client, err := kms.NewKeyManagementClient(ctx)
if err != nil {
return nil, err
}
return cloudkms.NewSigner(client, path)
} else {
buf, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
privateKeyBlock, _ := pem.Decode(buf)
if privateKeyBlock == nil {
return nil, fmt.Errorf("Failed to decode")
}
privateKey, err := x509.ParsePKCS8PrivateKey(privateKeyBlock.Bytes)
if err != nil {
return nil, err
}
switch key := privateKey.(type) {
case *rsa.PrivateKey:
return key, nil
default:
return nil, fmt.Errorf("not implemented yet")
}
}
}