-
Notifications
You must be signed in to change notification settings - Fork 17
Add the amikad sandbox daemon and the no-relay WebSocket SSH path
#316
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
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
fc01f30
Define no-relay SSH contracts
dbmikus 650b433
Specify no-relay SSH vertical slice
dbmikus dac6b6d
Tighten no-relay SSH contracts
dbmikus 329275b
amika annotations: amend this commit with changes
dbmikus d4bb156
Implement local no-relay SSH path
dbmikus 6279f28
Package amikad for sandbox images
dbmikus b565213
Harden SSH key handling
dbmikus f336617
Build amikad into base images
dbmikus 05c6df1
Fix no-relay SSH runtime lifecycle
dbmikus f864496
Pin fixed amikad in sandbox images
dbmikus f448546
Harden authorized key ownership
dbmikus 862cbbd
Pin reviewed amikad in base image
dbmikus 3fde13c
Close authorized key ownership race
dbmikus 4777f5c
Repin reviewed amikad source
dbmikus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,35 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "github.com/gofixpoint/amika/go/internal/runmode" | ||
| "github.com/gofixpoint/amika/go/internal/ssh" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var plumbingCmd = &cobra.Command{ | ||
| Use: "plumbing", | ||
| Short: "Internal machine-facing commands", | ||
| Hidden: true, | ||
| } | ||
|
|
||
| var sshStdioProxyCmd = &cobra.Command{ | ||
| Use: "ssh-stdio-proxy <host>", | ||
| Short: "Proxy standard IO to one SSH transport", | ||
| Hidden: true, | ||
| Args: cobra.ExactArgs(1), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return ssh.ProxySession( | ||
| cmd.Context(), | ||
| runmode.NewRemoteClient(), | ||
| ssh.WebSocketDialer{}, | ||
| args[0], | ||
| cmd.InOrStdin(), | ||
| cmd.OutOrStdout(), | ||
| ) | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| rootCmd.AddCommand(plumbingCmd) | ||
| plumbingCmd.AddCommand(sshStdioProxyCmd) | ||
| } |
This file contains hidden or 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 hidden or 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,85 @@ | ||
| package sandboxcmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
|
|
||
| "github.com/gofixpoint/amika/go/internal/basedir" | ||
| "github.com/gofixpoint/amika/go/internal/output" | ||
| "github.com/gofixpoint/amika/go/internal/runmode" | ||
| "github.com/gofixpoint/amika/go/internal/ssh" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var sandboxSSHV2Cmd = &cobra.Command{ | ||
| Use: "sshv2 [flags] <name> [-- <command>...]", | ||
| Short: "SSH through the beta direct WebSocket transport", | ||
| Args: cobra.MinimumNArgs(1), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| if runmode.Resolve(cmd) == runmode.Local { | ||
| return fmt.Errorf("direct WebSocket SSH requires a remote sandbox") | ||
| } | ||
| if err := runmode.RequireAuth(runmode.Remote, runmode.DefaultAuthChecker); err != nil { | ||
| return err | ||
| } | ||
| if err := output.RejectFlag(cmd); err != nil { | ||
| return err | ||
| } | ||
| target, err := getRemoteTarget(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| client, err := getRemoteClient(target) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| sandbox, err := client.GetSandbox(args[0]) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| alias, err := ssh.BuildSessionAlias(sandbox.Name, sandbox.ID) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| paths := basedir.New("") | ||
| state, err := ssh.LoadState(paths) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| var sessionConfig ssh.SessionConfig | ||
| if state.SessionConfig != nil { | ||
| sessionConfig = *state.SessionConfig | ||
| } else { | ||
| identityFile, err := paths.SSHIdentityFile() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| knownHostsFile, err := paths.SSHKnownHostsFile() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| sessionConfig = ssh.SessionConfig{ | ||
| IdentityFile: identityFile, | ||
| KnownHostsFile: knownHostsFile, | ||
| ProxyCommand: "amika plumbing ssh-stdio-proxy %h", | ||
| } | ||
| } | ||
| identityInfo, err := os.Stat(sessionConfig.IdentityFile) | ||
| if err != nil || !identityInfo.Mode().IsRegular() || identityInfo.Mode().Perm()&0o077 != 0 { | ||
| return fmt.Errorf("SSH identity is missing or unsafe; run \"amika secret ssh-keygen\"") | ||
| } | ||
| if err := ssh.ConfigureSession(paths, sessionConfig); err != nil { | ||
| return err | ||
| } | ||
| if _, err := ssh.PrepareSessionHost( | ||
| client, | ||
| ssh.FileHostKeyPinStore{Path: sessionConfig.KnownHostsFile}, | ||
| sandbox.ID, | ||
| alias, | ||
| ); err != nil { | ||
| return err | ||
| } | ||
| forcePTY, _ := cmd.Flags().GetBool("t") | ||
| return ssh.ExecSessionSSH(alias, forcePTY, args[1:]) | ||
| }, | ||
| } |
This file contains hidden or 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 hidden or 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,74 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/gofixpoint/amika/go/internal/apiclient" | ||
| "github.com/gofixpoint/amika/go/internal/basedir" | ||
| "github.com/gofixpoint/amika/go/internal/output" | ||
| "github.com/gofixpoint/amika/go/internal/runmode" | ||
| "github.com/gofixpoint/amika/go/internal/ssh" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newSSHKeygenCmd() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "ssh-keygen", | ||
| Short: "Create or import a user-owned SSH key", | ||
| Args: cobra.NoArgs, | ||
| RunE: func(cmd *cobra.Command, _ []string) error { | ||
| if err := runmode.RequireAuth(runmode.Remote, runmode.DefaultAuthChecker); err != nil { | ||
| return err | ||
| } | ||
| format, err := output.FormatFrom(cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| name, _ := cmd.Flags().GetString("name") | ||
| if strings.TrimSpace(name) == "" { | ||
| return fmt.Errorf("--name must not be empty") | ||
| } | ||
| paths := basedir.New("") | ||
| identityPath, err := paths.SSHIdentityFile() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| importPath, _ := cmd.Flags().GetString("import") | ||
| var publicKey string | ||
| if importPath == "" { | ||
| publicKey, err = ssh.GenerateIdentity(identityPath) | ||
| } else { | ||
| identityPath, publicKey, err = ssh.ImportIdentity(importPath) | ||
| } | ||
| if err != nil { | ||
| return err | ||
| } | ||
| knownHostsPath, err := paths.SSHKnownHostsFile() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if err := ssh.ConfigureSession(paths, ssh.SessionConfig{ | ||
| IdentityFile: identityPath, | ||
| KnownHostsFile: knownHostsPath, | ||
| ProxyCommand: "amika plumbing ssh-stdio-proxy %h", | ||
| }); err != nil { | ||
| return err | ||
| } | ||
| summary, err := runmode.NewRemoteClient().CreateSSHPublicKey( | ||
| apiclient.CreateSSHPublicKeyRequest{Name: name, PublicKey: publicKey}, | ||
| ) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if format.IsJSON() { | ||
| return format.JSON(cmd.OutOrStdout(), summary) | ||
| } | ||
| fmt.Fprintf(cmd.OutOrStdout(), "SSH public key %q uploaded; private key remains at %s.\n", summary.Name, identityPath) | ||
| return nil | ||
| }, | ||
| } | ||
| cmd.Flags().String("import", "", "Import an existing .pub file instead of generating a key") | ||
| cmd.Flags().String("name", "default", "Name for the uploaded public key") | ||
| return cmd | ||
| } | ||
This file contains hidden or 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,27 @@ | ||
| // Package main runs the Amika sandbox daemon. | ||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "os/signal" | ||
| "syscall" | ||
|
|
||
| "github.com/gofixpoint/amika/go/internal/amikad" | ||
| ) | ||
|
|
||
| func main() { | ||
| ctx, stop := signal.NotifyContext( | ||
| context.Background(), | ||
| os.Interrupt, | ||
| syscall.SIGTERM, | ||
| ) | ||
| defer stop() | ||
| cmd := amikad.NewCommand(amikad.NewProductionOperations()) | ||
| if err := cmd.ExecuteContext(ctx); err != nil && !errors.Is(err, context.Canceled) { | ||
| fmt.Fprintln(os.Stderr, err) | ||
| os.Exit(1) | ||
| } | ||
| } |
This file contains hidden or 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 hidden or 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 hidden or 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,12 @@ | ||
| //go:build !windows | ||
|
|
||
| package amikad | ||
|
|
||
| import ( | ||
| "os/exec" | ||
| "syscall" | ||
| ) | ||
|
|
||
| func configureBackgroundProcess(command *exec.Cmd) { | ||
| command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} | ||
| } |
This file contains hidden or 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,7 @@ | ||
| //go:build windows | ||
|
|
||
| package amikad | ||
|
|
||
| import "os/exec" | ||
|
|
||
| func configureBackgroundProcess(_ *exec.Cmd) {} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
--importis given a relative path such asid_ed25519.pub,ImportIdentityreturns the matching relative private-key path, but the subsequentConfigureSessioncall rejects it becauseRenderSessionConfigrequiresfilepath.IsAbs. The documented import mode therefore fails for an otherwise valid keypair in the current directory; convert the imported path to an absolute path before persisting the session configuration.Useful? React with 👍 / 👎.