Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8989dc25ac | ||
|
|
899aad07b2 | ||
|
|
2bc73596d3 | ||
|
|
00de399557 | ||
|
|
4de02548c1 | ||
|
|
fbca16fc9a | ||
|
|
d504f22925 | ||
|
|
3e77c30ed3 | ||
|
|
9573438f75 | ||
|
|
d9b0b2e21a | ||
|
|
78559f0bc7 | ||
|
|
0276f52b49 | ||
|
|
5d7886db40 | ||
|
|
a9a40622ca | ||
|
|
bcb5789044 | ||
|
|
da6ba96063 | ||
|
|
20c5cf00ea |
28
.pre-commit-config.yaml
Normal file
28
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
args: [--markdown-linebreak-ext=.gitignore]
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: go-imports
|
||||||
|
name: goimports
|
||||||
|
entry: goimports
|
||||||
|
args: [-w, -l]
|
||||||
|
language: golang
|
||||||
|
types: [go]
|
||||||
|
additional_dependencies: [golang.org/x/tools/cmd/goimports@latest]
|
||||||
|
- id: go-mod-tidy
|
||||||
|
name: go mod tidy
|
||||||
|
entry: go mod tidy
|
||||||
|
language: golang
|
||||||
|
types: [go-mod]
|
||||||
|
always_run: true
|
||||||
|
pass_filenames: false
|
||||||
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
|
rev: v1.52.2
|
||||||
|
hooks:
|
||||||
|
- id: golangci-lint
|
||||||
2
LICENSE
2
LICENSE
@@ -4,4 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|||||||
25
cmd/sshrimp-agent/logrotate.go
Normal file
25
cmd/sshrimp-agent/logrotate.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logRotate(path string, count int) {
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
ext := filepath.Ext(path)
|
||||||
|
base := strings.TrimSuffix(path, ext)
|
||||||
|
for i := count - 1; i >= 1; i-- {
|
||||||
|
source := fmt.Sprintf("%s.%d%s", base, i, ext)
|
||||||
|
dest := fmt.Sprintf("%s.%d%s", base, i+1, ext)
|
||||||
|
_ = os.Remove(dest)
|
||||||
|
_ = os.Rename(source, dest)
|
||||||
|
}
|
||||||
|
dest := fmt.Sprintf("%s.%d%s", base, 1, ext)
|
||||||
|
_ = os.Remove(dest)
|
||||||
|
_ = os.Rename(path, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -11,116 +11,352 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/signer"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/signer"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/sshrimpagent"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/sshrimpagent"
|
||||||
|
"github.com/prometheus/procfs"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/sirupsen/logrus/hooks/writer"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/agent"
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
"inet.af/peercred"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sigs = []os.Signal{os.Kill, os.Interrupt}
|
var (
|
||||||
var logger = logrus.New()
|
sigExit = []os.Signal{os.Kill, os.Interrupt}
|
||||||
var log *logrus.Entry
|
sigIgnore []os.Signal
|
||||||
|
logger = logrus.New()
|
||||||
|
log *logrus.Entry
|
||||||
|
appname = "sshrimp"
|
||||||
|
)
|
||||||
|
|
||||||
var cli struct {
|
type cfg struct {
|
||||||
Config string `kong:"arg,type='string',help='sshrimp config file (default: ${config_file} or ${env_var_name} environment variable)',default='${config_file}',env='SSHRIMP_CONFIG'"`
|
Config string
|
||||||
|
LogDirectory string
|
||||||
|
Verbose bool
|
||||||
|
Foreground bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func getLogDir() string {
|
||||||
flag.StringVar(&cli.Config, "config", config.DefaultPath, "sshrimp config file")
|
logdir := ""
|
||||||
v := flag.Bool("v", false, "enable verbose logging")
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "plan9":
|
||||||
|
if dir, err := os.UserConfigDir(); err == nil {
|
||||||
|
logdir = filepath.Join(dir, "logs", appname)
|
||||||
|
}
|
||||||
|
case "darwin", "ios":
|
||||||
|
if dir, err := os.UserHomeDir(); err == nil {
|
||||||
|
logdir = filepath.Join(dir, "Library/Logs", appname)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if dir, err := os.UserCacheDir(); err == nil {
|
||||||
|
logdir = filepath.Join(dir, appname, "logs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if logdir == "" {
|
||||||
|
if dir, err := os.UserHomeDir(); err == nil {
|
||||||
|
logdir = filepath.Join(dir, ".ssh/sshrimp_logs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return logdir
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupLoging(config cfg) error {
|
||||||
|
levels := []logrus.Level{
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.FatalLevel,
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.WarnLevel,
|
||||||
|
}
|
||||||
|
err := os.MkdirAll(config.LogDirectory, 0o750)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logName := filepath.Join(config.LogDirectory, appname+".log")
|
||||||
|
logRotate(logName, 10)
|
||||||
|
logger.SetLevel(logrus.TraceLevel)
|
||||||
|
if config.Verbose {
|
||||||
|
levels = logrus.AllLevels
|
||||||
|
}
|
||||||
|
|
||||||
logger.SetFormatter(&logrus.TextFormatter{
|
logger.SetFormatter(&logrus.TextFormatter{
|
||||||
FullTimestamp: true,
|
FullTimestamp: true,
|
||||||
})
|
})
|
||||||
|
logger.AddHook(&writer.Hook{ // Send logs with level higher than warning to stderr
|
||||||
|
Writer: os.Stderr,
|
||||||
|
LogLevels: levels,
|
||||||
|
})
|
||||||
|
logger.Out = io.Discard
|
||||||
|
file, err := os.Create(logName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// defer file.Close()
|
||||||
|
logger.AddHook(&writer.Hook{ // Send all logs to file
|
||||||
|
Writer: file,
|
||||||
|
LogLevels: logrus.AllLevels,
|
||||||
|
})
|
||||||
log = logger.WithFields(logrus.Fields{
|
log = logger.WithFields(logrus.Fields{
|
||||||
"pid": os.Getpid(),
|
"pid": os.Getpid(),
|
||||||
})
|
})
|
||||||
|
|
||||||
sshrimpagent.Log = log
|
sshrimpagent.Log = log
|
||||||
signer.Log = log
|
signer.Log = log
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
flag.Parse()
|
func ExpandPath(path string) string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
if *v {
|
if err != nil {
|
||||||
logger.SetLevel(logrus.TraceLevel)
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
c := config.NewSSHrimpWithDefaults()
|
if path[0] == '~' {
|
||||||
err := c.Read(cli.Config)
|
path = filepath.Join(home, path[1:])
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
err = launchAgent(c)
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func main2(cli cfg, c *config.SSHrimp) {
|
||||||
|
err := setupLoging(cli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Warnf("Error setting up logging: %v", err)
|
||||||
|
}
|
||||||
|
listener := openSocket(ExpandPath(c.Agent.Socket))
|
||||||
|
if listener == nil {
|
||||||
|
log.Errorln("Failed to open socket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = launchAgent(c, listener)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("Failed to launch agent", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func launchAgent(c *config.SSHrimp) error {
|
func main() {
|
||||||
|
defaultConfigPath := "~/.ssh/sshrimp.toml"
|
||||||
|
if configPathFromEnv, ok := os.LookupEnv("SSHRIMP_CONFIG"); ok && configPathFromEnv != "" {
|
||||||
|
defaultConfigPath = configPathFromEnv
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
cli cfg
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
flag.StringVar(&cli.Config, "config", defaultConfigPath, "sshrimp config file")
|
||||||
|
flag.StringVar(&cli.LogDirectory, "log", getLogDir(), "sshrimp log directory")
|
||||||
|
flag.BoolVar(&cli.Verbose, "v", false, "enable verbose logging")
|
||||||
|
flag.BoolVar(&cli.Foreground, "f", false, "Run in the foreground")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
sshCommand := flag.Args()
|
||||||
|
|
||||||
|
cfgFile := ExpandPath(cli.Config)
|
||||||
|
cfgFile, err = filepath.Abs(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("config must be an absolute path")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
c := config.NewSSHrimpWithDefaults()
|
||||||
|
err = c.Read(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if os.Getenv("SSHRIMP_DAEMON") == "true" {
|
||||||
|
cli.Foreground = true
|
||||||
|
}
|
||||||
|
if cli.Foreground {
|
||||||
|
logger.Println("Launching agent")
|
||||||
|
main2(cli, c)
|
||||||
|
} else {
|
||||||
|
logger.Println("Attempting to start daemon")
|
||||||
|
var nullFile *os.File
|
||||||
|
nullFile, err = os.Open(os.DevNull)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
env := os.Environ()
|
||||||
|
env = append(env, "SSHRIMP_DAEMON=true")
|
||||||
|
executable, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = os.StartProcess(executable, os.Args, &os.ProcAttr{
|
||||||
|
Dir: filepath.Dir(cfgFile),
|
||||||
|
Env: env,
|
||||||
|
Files: []*os.File{nullFile, nullFile, nullFile},
|
||||||
|
Sys: &syscall.SysProcAttr{
|
||||||
|
// Chroot: d.Chroot,
|
||||||
|
Setsid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
nullFile.Close()
|
||||||
|
}
|
||||||
|
if len(sshCommand) > 1 && filepath.Base(sshCommand[0]) == "ssh" {
|
||||||
|
syscall.Exec(sshCommand[0], sshCommand, os.Environ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func socketWorks(path string) bool {
|
||||||
|
var (
|
||||||
|
pid int
|
||||||
|
cred *peercred.Creds
|
||||||
|
)
|
||||||
|
conn, sockErr := net.Dial("unix", path)
|
||||||
|
if sockErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
cred, sockErr = peercred.Get(conn)
|
||||||
|
if sockErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ok bool
|
||||||
|
process *os.Process
|
||||||
|
)
|
||||||
|
pid, ok = cred.PID()
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
process, sockErr = os.FindProcess(pid)
|
||||||
|
if sockErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer process.Release()
|
||||||
|
return process.Signal(syscall.SIGHUP) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openSocket(socketPath string) net.Listener {
|
||||||
var (
|
var (
|
||||||
err error
|
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
privateKey crypto.Signer
|
err error
|
||||||
signer ssh.Signer
|
|
||||||
logMessage string
|
logMessage string
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Traceln("Creating socket")
|
if socketWorks(socketPath) { // socket is accepting connections
|
||||||
if _, err = os.Stat(c.Agent.Socket); err == nil {
|
log.Printf("socket %s already exists\n", socketPath)
|
||||||
log.Tracef("File already exists at %s", c.Agent.Socket)
|
return nil
|
||||||
conn, sockErr := net.Dial("unix", c.Agent.Socket)
|
}
|
||||||
if conn == nil {
|
log.Printf("Socket is not connected %s\n", logMessage)
|
||||||
logMessage = "conn is nil"
|
err = os.Remove(socketPath)
|
||||||
}
|
if err == nil { // socket is not accepting connections, assuming safe to remove
|
||||||
if sockErr == nil { // socket is accepting connections
|
log.Println("Deleting socket: success")
|
||||||
logMessage += "err reports successful connection"
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
conn.Close()
|
log.Println("Deleting socket: fail", err)
|
||||||
log.Errorf("Socket connected successfully %s", logMessage)
|
return nil
|
||||||
return fmt.Errorf("socket %s already exists", c.Agent.Socket)
|
|
||||||
}
|
|
||||||
log.Tracef("Socket is not connected %s", logMessage)
|
|
||||||
if os.Remove(c.Agent.Socket) == nil { // socket is not accepting connections, assuming safe to remove
|
|
||||||
log.Traceln("Deleting socket: success")
|
|
||||||
} else {
|
|
||||||
log.Errorf("Deleting socket: fail")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This affects all files created for the process. Since this is a sensitive
|
// This affects all files created for the process. Since this is a sensitive
|
||||||
// socket, only allow the current user to write to the socket.
|
// socket, only allow the current user to write to the socket.
|
||||||
syscall.Umask(0077)
|
syscall.Umask(0o077)
|
||||||
listener, err = net.Listen("unix", c.Agent.Socket)
|
listener, err = net.Listen("unix", socketPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("Error opening socket:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Println("Opened socket", socketPath)
|
||||||
|
return listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnectedProcess(conn net.Conn) string {
|
||||||
|
var (
|
||||||
|
cred *peercred.Creds
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
cred, err = peercred.Get(conn)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
pid, ok := cred.PID()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
proc procfs.Proc
|
||||||
|
name string
|
||||||
|
)
|
||||||
|
proc, err = procfs.NewProc(pid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("pid %d", pid)
|
||||||
|
}
|
||||||
|
name, err = proc.Executable()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Sprintf("pid %d", pid)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(sshrimpAgent agent.Agent, conn net.Conn) (err error) {
|
||||||
|
defer func() {
|
||||||
|
panicErr := recover()
|
||||||
|
|
||||||
|
if panicErr != nil {
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("something panicked: %w: %v", err, panicErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err, _ = panicErr.(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.Infof("Serving agent to %s", getConnectedProcess(conn))
|
||||||
|
if err = agent.ServeAgent(sshrimpAgent, conn); err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
log.Errorf("Error serving agent: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func launchAgent(c *config.SSHrimp, listener net.Listener) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
privateKey crypto.Signer
|
||||||
|
sshSigner ssh.Signer
|
||||||
|
)
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
fmt.Printf("listening on %s\n", c.Agent.Socket)
|
log.Printf("listening on %s\n", c.Agent.Socket)
|
||||||
|
|
||||||
// Generate a new SSH private/public key pair
|
// Generate a new SSH private/public key pair
|
||||||
log.Tracef("Generating RSA %d ssh keys", 2048)
|
log.Tracef("Generating ed25519 ssh keys")
|
||||||
privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
|
_, privateKey, err = ed25519.GenerateKey(rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Traceln("Creating new signer from key")
|
log.Traceln("Creating new sshSigner from key")
|
||||||
signer, err = ssh.NewSignerFromKey(privateKey)
|
sshSigner, err = ssh.NewSignerFromKey(privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the sshrimp agent with our configuration and the private key signer
|
// Create the sshrimp agent with our configuration and the private key sshSigner
|
||||||
log.Traceln("Creating new sshrimp agent from signer and config")
|
log.Traceln("Creating new sshrimp agent from sshSigner and config")
|
||||||
sshrimpAgent := sshrimpagent.NewSSHrimpAgent(c, signer)
|
sshrimpAgent, err := sshrimpagent.NewSSHrimpAgent(c, sshSigner)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create sshrimpAgent: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for signals so that we can close the listener and exit nicely
|
// Listen for signals so that we can close the listener and exit nicely
|
||||||
log.Debugf("Exiting on signals: %v", sigs)
|
log.Debugf("Ignoring signals: %v", sigIgnore)
|
||||||
osSignals := make(chan os.Signal)
|
signal.Ignore(sigIgnore...)
|
||||||
signal.Notify(osSignals, sigs...)
|
log.Debugf("Exiting on signals: %v", sigExit)
|
||||||
|
osSignals := make(chan os.Signal, 10)
|
||||||
|
signal.Notify(osSignals, sigExit...)
|
||||||
go func() {
|
go func() {
|
||||||
<-osSignals
|
<-osSignals
|
||||||
listener.Close()
|
listener.Close()
|
||||||
@@ -135,14 +371,10 @@ func launchAgent(c *config.SSHrimp) error {
|
|||||||
log.Errorf("Error accepting connection: %v", err)
|
log.Errorf("Error accepting connection: %v", err)
|
||||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
// Occurs if the user interrupts the agent with a ctrl-c signal
|
// Occurs if the user interrupts the agent with a ctrl-c signal
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
return err
|
log.Errorf("Error accepting connection: %v", err)
|
||||||
}
|
|
||||||
log.Traceln("Serving agent")
|
|
||||||
if err = agent.ServeAgent(sshrimpAgent, conn); err != nil && !errors.Is(err, io.EOF) {
|
|
||||||
log.Errorf("Error serving agent: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
go handle(sshrimpAgent, conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build darwin linux
|
//go:build darwin || linux
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@@ -7,5 +7,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sigs = append(sigs, syscall.SIGTERM)
|
sigExit = append(sigExit, syscall.SIGTERM)
|
||||||
|
sigIgnore = append(sigIgnore, syscall.SIGHUP)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,182 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"bytes"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/signer"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/signer"
|
||||||
"github.com/aws/aws-lambda-go/lambda"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/aws/aws-lambda-go/lambdacontext"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleRequest handles a request to sign an SSH public key verified by an OpenIDConnect id_token
|
func httpError(w http.ResponseWriter, v any, statusCode int) {
|
||||||
func HandleRequest(ctx context.Context, event signer.SSHrimpEvent) (*signer.SSHrimpResult, error) {
|
var b bytes.Buffer
|
||||||
|
e := json.NewEncoder(&b)
|
||||||
|
_ = e.Encode(v)
|
||||||
|
http.Error(w, b.String(), statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure we are running in a lambda context, to get the requestid and ARN
|
type Server struct {
|
||||||
lambdaContext, ok := lambdacontext.FromContext(ctx)
|
config *config.SSHrimp
|
||||||
if !ok {
|
Key ssh.Signer
|
||||||
return nil, fmt.Errorf("lambdacontext not in ctx")
|
Log *logrus.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(cfg *config.SSHrimp) (*Server, error) {
|
||||||
|
server := &Server{
|
||||||
|
config: cfg,
|
||||||
|
Log: logrus.New(),
|
||||||
|
}
|
||||||
|
server.Log.SetLevel(logrus.DebugLevel)
|
||||||
|
return server, server.LoadKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) LoadKey() error {
|
||||||
|
if s.config.CertificateAuthority.KeyPath == "" {
|
||||||
|
return fmt.Errorf("key path missing")
|
||||||
|
}
|
||||||
|
b, err := os.ReadFile(s.config.CertificateAuthority.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return s.GenerateKey()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Key, err = ssh.ParsePrivateKey(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GenerateKey() error {
|
||||||
|
_, key, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(s.config.CertificateAuthority.KeyPath, os.O_RDWR|os.O_CREATE, 0o400)
|
||||||
|
if err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var pkcs8 []byte
|
||||||
|
if pkcs8, err = x509.MarshalPKCS8PrivateKey(key); err == nil {
|
||||||
|
err = pem.Encode(file, &pem.Block{
|
||||||
|
Type: "PRIVATE KEY",
|
||||||
|
Bytes: pkcs8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not save generated CertificateAuthority key: %v", err)
|
||||||
|
}
|
||||||
|
s.Key, err = ssh.NewSignerFromKey(key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP handles a request to sign an SSH public key verified by an OpenIDConnect id_token
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
txid := gonanoid.Must()
|
||||||
|
log := s.Log.WithField("X-Request-ID", txid)
|
||||||
|
w.Header().Add("X-Request-ID", txid)
|
||||||
|
defer r.Body.Close()
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/config") {
|
||||||
|
io.Copy(io.Discard, r.Body)
|
||||||
|
newConfig := *s.config
|
||||||
|
newConfig.CertificateAuthority = config.CertificateAuthority{}
|
||||||
|
w.Header().Add("Content-Type", "application/toml")
|
||||||
|
w.Header().Add("Content-Disposition", `attachment; filename="sshrimp.toml"`)
|
||||||
|
t := toml.NewEncoder(w)
|
||||||
|
_ = t.Encode(newConfig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Header.Get("Content-Type") != "application/json" {
|
||||||
|
io.Copy(io.Discard, r.Body)
|
||||||
|
w.Header().Add("Content-Type", "text/x-ssh-public-key")
|
||||||
|
w.Write(ssh.MarshalAuthorizedKey(s.Key.PublicKey()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
// Load the configuration file, if not exsits, exit.
|
// Load the configuration file, if not exsits, exit.
|
||||||
c := config.NewSSHrimp()
|
var event signer.SSHrimpEvent
|
||||||
if err := c.Read(config.GetPath()); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||||||
return nil, err
|
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the certificate struct with all our configured values
|
certificate, err := signer.ValidateRequest(log, event, s.config, txid, s.Key.PublicKey())
|
||||||
certificate, err := signer.ValidateRequest(event, c, lambdaContext.AwsRequestID, lambdaContext.InvokedFunctionArn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup our Certificate Authority signer backed by KMS
|
sshAlgorithmSigner, err := ssh.NewSignerWithAlgorithms(s.Key.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoED25519})
|
||||||
kmsSigner := signer.NewAWSSigner(c.CertificateAuthority.KeyAlias)
|
|
||||||
sshAlgorithmSigner, err := signer.NewAlgorithmSignerFromSigner(kmsSigner, ssh.SigAlgoRSASHA2256)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign the certificate!!
|
// Sign the certificate!!
|
||||||
if err := certificate.SignCert(rand.Reader, sshAlgorithmSigner); err != nil {
|
if err := certificate.SignCert(rand.Reader, sshAlgorithmSigner); err != nil {
|
||||||
return nil, err
|
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusInternalServerError)}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the public key (certificate) to return to the user
|
// If you want to validate that the generated cert is correct
|
||||||
|
// i, _ := identity.NewIdentity(s.config)
|
||||||
|
// username, _ := i.Validate(event.Token)
|
||||||
|
// cc := ssh.CertChecker{}
|
||||||
|
// err = cc.CheckCert(username, &certificate)
|
||||||
|
// if err != nil {
|
||||||
|
// httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusInternalServerError)}, http.StatusBadRequest)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Return the certificate to the user
|
||||||
pubkey, err := ssh.ParsePublicKey(certificate.Marshal())
|
pubkey, err := ssh.ParsePublicKey(certificate.Marshal())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusInternalServerError)}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success!
|
// Success!
|
||||||
return &signer.SSHrimpResult{
|
res := &signer.SSHrimpResult{
|
||||||
Certificate: string(ssh.MarshalAuthorizedKey(pubkey)),
|
Certificate: string(ssh.MarshalAuthorizedKey(pubkey)),
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
ErrorType: "",
|
ErrorType: "",
|
||||||
}, nil
|
}
|
||||||
|
e := json.NewEncoder(w)
|
||||||
|
_ = e.Encode(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
lambda.Start(HandleRequest)
|
cfgFile := flag.String("config", "/etc/sshrimp.toml", "Path to sshrimp.toml")
|
||||||
|
addr := flag.String("addr", "127.0.0.1:8080", "Address to listen on")
|
||||||
|
flag.Parse()
|
||||||
|
cfg := config.NewSSHrimp()
|
||||||
|
if err := cfg.Read(*cfgFile); err != nil {
|
||||||
|
log.Printf("Unable to read config file %s: %v", *cfgFile, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
server, err := NewServer(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to start server: %v", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if err = http.ListenAndServe(*addr, server); err != nil {
|
||||||
|
log.Printf("Error serving http: %v", err)
|
||||||
|
os.Exit(99)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
gcp/gcp.go
83
gcp/gcp.go
@@ -1,83 +0,0 @@
|
|||||||
package gcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/identity"
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/signer"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
func httpError(w http.ResponseWriter, v interface{}, statusCode int) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
e := json.NewEncoder(&b)
|
|
||||||
_ = e.Encode(v)
|
|
||||||
http.Error(w, b.String(), statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSHrimp handles a request to sign an SSH public key verified by an OpenIDConnect id_token
|
|
||||||
func SSHrimp(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Load the configuration file, if not exsits, exit.
|
|
||||||
c := config.NewSSHrimp()
|
|
||||||
if err := c.Read("./serverless_function_source_code/sshrimp.toml"); err != nil {
|
|
||||||
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusInternalServerError)}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var event signer.SSHrimpEvent
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
|
||||||
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate, err := signer.ValidateRequest(event, c, r.Header.Get("Function-Execution-Id"), fmt.Sprintf("%s/%s/%s", os.Getenv("GCP_PROJECT"), os.Getenv("FUNCTION_REGION"), os.Getenv("FUNCTION_NAME")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup our Certificate Authority signer backed by KMS
|
|
||||||
kmsSigner := signer.NewGCPSSigner(c.CertificateAuthority.KeyAlias)
|
|
||||||
|
|
||||||
sshAlgorithmSigner, err := signer.NewAlgorithmSignerFromSigner(kmsSigner, ssh.SigAlgoRSASHA2256)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusBadRequest)}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign the certificate!!
|
|
||||||
if err := certificate.SignCert(rand.Reader, sshAlgorithmSigner); err != nil {
|
|
||||||
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusInternalServerError)}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
i, _ := identity.NewIdentity(c)
|
|
||||||
username, _ := i.Validate(event.Token)
|
|
||||||
cc := ssh.CertChecker{}
|
|
||||||
err = cc.CheckCert(username, &certificate)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusInternalServerError)}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the public key (certificate) to return to the user
|
|
||||||
pubkey, err := ssh.ParsePublicKey(certificate.Marshal())
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, signer.SSHrimpResult{Certificate: "", ErrorMessage: err.Error(), ErrorType: http.StatusText(http.StatusInternalServerError)}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success!
|
|
||||||
res := &signer.SSHrimpResult{
|
|
||||||
Certificate: string(ssh.MarshalAuthorizedKey(pubkey)),
|
|
||||||
ErrorMessage: "",
|
|
||||||
ErrorType: "",
|
|
||||||
}
|
|
||||||
e := json.NewEncoder(w)
|
|
||||||
_ = e.Encode(res)
|
|
||||||
}
|
|
||||||
46
go.mod
46
go.mod
@@ -1,22 +1,34 @@
|
|||||||
module git.narnian.us/lordwelch/sshrimp
|
module gitea.narnian.us/lordwelch/sshrimp
|
||||||
|
|
||||||
go 1.13
|
go 1.24.0
|
||||||
|
|
||||||
replace github.com/b-b3rn4rd/gocfn => github.com/stoggi/gocfn v0.0.0-20200214083946-6202cea979b9
|
toolchain go1.24.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.73.0
|
github.com/BurntSushi/toml v1.6.0
|
||||||
git.narnian.us/lordwelch/aws-oidc v0.0.2
|
github.com/coreos/go-oidc/v3 v3.17.0
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.4
|
github.com/google/uuid v1.6.0
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||||
github.com/aws/aws-lambda-go v1.19.0
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
github.com/aws/aws-sdk-go v1.36.2
|
github.com/prometheus/procfs v0.19.2
|
||||||
github.com/awslabs/goformation/v4 v4.14.0
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/zitadel/oidc v1.13.5
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
golang.org/x/crypto v0.46.0
|
||||||
github.com/magefile/mage v1.10.0
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
|
||||||
github.com/pkg/errors v0.9.1
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||||
github.com/sirupsen/logrus v1.7.0
|
)
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
|
||||||
google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497
|
require (
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||||
|
github.com/gorilla/schema v1.4.1 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/jeremija/gosubmit v0.2.8 // indirect
|
||||||
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
|
github.com/zitadel/logging v0.6.2 // indirect
|
||||||
|
golang.org/x/net v0.48.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.34.0 // indirect
|
||||||
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
|
golang.org/x/text v0.32.0 // indirect
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
603
go.sum
603
go.sum
@@ -1,555 +1,72 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
|
||||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
|
||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
|
||||||
cloud.google.com/go v0.73.0 h1:sGvc4e0Cmm4+DKQR76a9VwNukpacQK8TOl5pDl0Pcn0=
|
|
||||||
cloud.google.com/go v0.73.0/go.mod h1:BkDh9dFvGjCitVw03TNjKbBxXNKULXXIq6orU6HrJ4Q=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
git.narnian.us/lordwelch/aws-oidc v0.0.2 h1:75AOGD8IYvKpg772n4/4vku84kHOQETmQqp0YVLS/xw=
|
|
||||||
git.narnian.us/lordwelch/aws-oidc v0.0.2/go.mod h1:lfAaTDbI5Ip4TjBDAvJnPNmSLCMAS9hNRWgqwmm4bTk=
|
|
||||||
github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f h1:WXiWWJrYCaOaYimBAXlRdRJ7qOisrYyMLYnCvvhHVms=
|
|
||||||
github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f/go.mod h1:aKt8W/yd91/xHY6ixZAJZ2vYbhr3pP8DcrvuGSGNPJk=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.4 h1:OAh6g17JmXsjVVHTnfQFEi6K+YZX6mrC+pT8IPkUlpk=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.4/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/aulanov/go.dbus v0.0.0-20150729231527-25c3068a42a0 h1:EEDvbomAQ+MFWqJ9FM6RXyJTkc4lckyWsbc5CGQkG1Y=
|
|
||||||
github.com/aulanov/go.dbus v0.0.0-20150729231527-25c3068a42a0/go.mod h1:VHvUx+4lTCaJ8zUnEXF4cWEc9c8lnDt4PGLwlZ+3yaM=
|
|
||||||
github.com/aws/aws-lambda-go v1.19.0 h1:Cn28zA8Mic4NpR7p4IlaEW2srI+U3+I7tRqjFMpt/fs=
|
|
||||||
github.com/aws/aws-lambda-go v1.19.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU=
|
|
||||||
github.com/aws/aws-sdk-go v1.19.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
|
||||||
github.com/aws/aws-sdk-go v1.36.2 h1:UAeFPct+jHqWM+tgiqDrC9/sfbWj6wkcvpsJ+zdcsvA=
|
|
||||||
github.com/aws/aws-sdk-go v1.36.2/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
|
||||||
github.com/awslabs/goformation/v4 v4.14.0 h1:E2Pet9eIqA4qzt3dzzzE4YN83V4Kyfbcio0VokBC9TA=
|
|
||||||
github.com/awslabs/goformation/v4 v4.14.0/go.mod h1:GcJULxCJfloT+3pbqCluXftdEK2AD/UqpS3hkaaBntg=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5LbakNExNmZIg=
|
|
||||||
github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/danieljoos/wincred v1.0.1 h1:fcRTaj17zzROVqni2FiToKUVg3MmJ4NtMSGCySPIr/g=
|
|
||||||
github.com/danieljoos/wincred v1.0.1/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
|
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU=
|
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||||
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
|
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20201117184057-ae444373da19/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
|
||||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
|
||||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20190408194155-7f2ef9fddce6 h1:hfM5TYph19rQBp3oOg4SVckf4ZmYrycciBJCWmxOcIE=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20190408194155-7f2ef9fddce6/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
|
|
||||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
|
||||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
|
|
||||||
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
|
||||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/onsi/ginkgo v1.5.0 h1:uZr+v/TFDdYkdA+j02sPO1kA5owrfjBGCJAogfIyThE=
|
|
||||||
github.com/onsi/ginkgo v1.5.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/gomega v1.2.0 h1:tQjc4uvqBp0z424R9V/S2L18penoUiwZftoY0t48IZ4=
|
|
||||||
github.com/onsi/gomega v1.2.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
|
|
||||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
|
|
||||||
github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 h1:fOCp11H0yuyAt2wqlbJtbyPzSgaxHTv8uN1pMpkG1t8=
|
|
||||||
github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522/go.mod h1:tQTYKOQgxoH3v6dEmdHiz4JG+nbxWwM5fgPQUpSZqVQ=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/zitadel/oidc v1.13.5 h1:7jhh68NGZitLqwLiVU9Dtwa4IraJPFF1vS+4UupO93U=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/zitadel/oidc v1.13.5/go.mod h1:rHs1DhU3Sv3tnI6bQRVlFa3u0lCwtR7S21WHY+yXgPA=
|
||||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
|
||||||
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56 h1:yhqBHs09SmmUoNOHc9jgK4a60T3XFRtPAkYxVnqgY50=
|
|
||||||
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 h1:Mj83v+wSRNEar42a/MQgxk9X42TdEmrOl9i+y8WbxLo=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64=
|
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
|
||||||
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
|
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE=
|
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
|
||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497 h1:jDYzwXmX9tLnuG4sL85HPmE1ruErXOopALp2i/0AHnI=
|
|
||||||
google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.3.0 h1:nLzhkFyl5bkblqYBoiWJUt5JkWOzmiaBtCxdJAqJd3U=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.3.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
|
|||||||
@@ -10,31 +10,28 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/kballard/go-shellquote"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Agent config for the sshrimp-agent agent
|
// Agent config for the sshrimp-agent agent
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
ProviderURL string
|
ProviderURL string
|
||||||
ClientID string
|
ClientID string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
BrowserCommand []string
|
Socket string
|
||||||
Socket string
|
Scopes []string
|
||||||
|
KeyPath string
|
||||||
|
Port int
|
||||||
|
CAUrls []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CertificateAuthority config for the sshrimp-ca lambda
|
// CertificateAuthority config for the sshrimp-ca lambda
|
||||||
type CertificateAuthority struct {
|
type CertificateAuthority struct {
|
||||||
Project string
|
KeyPath string
|
||||||
AccountID int
|
|
||||||
Regions []string
|
|
||||||
FunctionName string
|
|
||||||
KeyAlias string
|
|
||||||
ForceCommandRegex string
|
ForceCommandRegex string
|
||||||
SourceAddressRegex string
|
SourceAddressRegex string
|
||||||
UsernameRegex string
|
UsernameRegexs []string
|
||||||
UsernameClaim string
|
UsernameClaims []string
|
||||||
ValidAfterOffset string
|
ValidAfterOffset string
|
||||||
ValidBeforeOffset string
|
ValidBeforeOffset string
|
||||||
Extensions []string
|
Extensions []string
|
||||||
@@ -46,39 +43,6 @@ type SSHrimp struct {
|
|||||||
CertificateAuthority CertificateAuthority
|
CertificateAuthority CertificateAuthority
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of supported regions for the config wizard
|
|
||||||
var SupportedAwsRegions = []string{
|
|
||||||
"ap-east-1",
|
|
||||||
"ap-northeast-1",
|
|
||||||
"ap-northeast-2",
|
|
||||||
"ap-south-1",
|
|
||||||
"ap-southeast-1",
|
|
||||||
"ap-southeast-2",
|
|
||||||
"ca-central-1",
|
|
||||||
"eu-central-1",
|
|
||||||
"eu-north-1",
|
|
||||||
"eu-west-1",
|
|
||||||
"eu-west-2",
|
|
||||||
"eu-west-3",
|
|
||||||
"me-south-1",
|
|
||||||
"sa-east-1",
|
|
||||||
"us-east-1",
|
|
||||||
"us-east-2",
|
|
||||||
"us-west-1",
|
|
||||||
"us-west-2",
|
|
||||||
}
|
|
||||||
|
|
||||||
var SupportedGcpRegions = []string{
|
|
||||||
"europe-west1",
|
|
||||||
"europe-west2",
|
|
||||||
"europe-west3",
|
|
||||||
"us-central1",
|
|
||||||
"us-east1",
|
|
||||||
"us-east4",
|
|
||||||
"asia-northeast1",
|
|
||||||
"asia-east2",
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedExtensions = []string{
|
var supportedExtensions = []string{
|
||||||
"no-agent-forwarding",
|
"no-agent-forwarding",
|
||||||
"no-port-forwarding",
|
"no-port-forwarding",
|
||||||
@@ -99,19 +63,17 @@ func NewSSHrimp() *SSHrimp {
|
|||||||
|
|
||||||
// NewSSHrimpWithDefaults returns SSHrimp with defaults already set
|
// NewSSHrimpWithDefaults returns SSHrimp with defaults already set
|
||||||
func NewSSHrimpWithDefaults() *SSHrimp {
|
func NewSSHrimpWithDefaults() *SSHrimp {
|
||||||
|
|
||||||
sshrimp := SSHrimp{
|
sshrimp := SSHrimp{
|
||||||
Agent{
|
Agent{
|
||||||
ProviderURL: "https://accounts.google.com",
|
ProviderURL: "https://accounts.google.com",
|
||||||
Socket: "/tmp/sshrimp.sock",
|
Socket: "~/.ssh/sshrimp.sock",
|
||||||
|
Scopes: []string{"openid", "email", "profile"},
|
||||||
},
|
},
|
||||||
CertificateAuthority{
|
CertificateAuthority{
|
||||||
FunctionName: "sshrimp",
|
|
||||||
KeyAlias: "alias/sshrimp",
|
|
||||||
ForceCommandRegex: "^$",
|
ForceCommandRegex: "^$",
|
||||||
SourceAddressRegex: "^$",
|
SourceAddressRegex: "^$",
|
||||||
UsernameRegex: `^(.*)@example\.com$`,
|
UsernameRegexs: []string{`^(.*)@example\.com$`},
|
||||||
UsernameClaim: "email",
|
UsernameClaims: []string{"email"},
|
||||||
ValidAfterOffset: "-5m",
|
ValidAfterOffset: "-5m",
|
||||||
ValidBeforeOffset: "+12h",
|
ValidBeforeOffset: "+12h",
|
||||||
Extensions: []string{
|
Extensions: []string{
|
||||||
@@ -126,21 +88,7 @@ func NewSSHrimpWithDefaults() *SSHrimp {
|
|||||||
return &sshrimp
|
return &sshrimp
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultPath of the sshrimp config file
|
func validateInt(val any) error {
|
||||||
var DefaultPath = "./sshrimp.toml"
|
|
||||||
|
|
||||||
// EnvVarName is the optional environment variable that if set overrides DefaultPath
|
|
||||||
var EnvVarName = "SSHRIMP_CONFIG"
|
|
||||||
|
|
||||||
// GetPath returns the default sshrimp config file path taking into account EnvVarName
|
|
||||||
func GetPath() string {
|
|
||||||
if configPathFromEnv, ok := os.LookupEnv(EnvVarName); ok && configPathFromEnv != "" {
|
|
||||||
return configPathFromEnv
|
|
||||||
}
|
|
||||||
return DefaultPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateInt(val interface{}) error {
|
|
||||||
if str, ok := val.(string); ok {
|
if str, ok := val.(string); ok {
|
||||||
if _, err := strconv.Atoi(str); err != nil {
|
if _, err := strconv.Atoi(str); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -152,7 +100,7 @@ func validateInt(val interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateURL(val interface{}) error {
|
func validateURL(val any) error {
|
||||||
if str, ok := val.(string); ok {
|
if str, ok := val.(string); ok {
|
||||||
if _, err := url.ParseRequestURI(str); err != nil {
|
if _, err := url.ParseRequestURI(str); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -164,7 +112,7 @@ func validateURL(val interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDuration(val interface{}) error {
|
func validateDuration(val any) error {
|
||||||
if str, ok := val.(string); ok {
|
if str, ok := val.(string); ok {
|
||||||
if _, err := time.ParseDuration(str); err != nil {
|
if _, err := time.ParseDuration(str); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -176,7 +124,7 @@ func validateDuration(val interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAlias(val interface{}) error {
|
func validateAlias(val any) error {
|
||||||
if str, ok := val.(string); ok {
|
if str, ok := val.(string); ok {
|
||||||
if !strings.HasPrefix(str, "alias/") {
|
if !strings.HasPrefix(str, "alias/") {
|
||||||
return errors.New("KMS alias must begin with alias/")
|
return errors.New("KMS alias must begin with alias/")
|
||||||
@@ -188,200 +136,6 @@ func validateAlias(val interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func certificateAuthorityQuestions(config *SSHrimp) []*survey.Question {
|
|
||||||
defaultAccountID := ""
|
|
||||||
if config.CertificateAuthority.AccountID > 0 {
|
|
||||||
defaultAccountID = strconv.Itoa(config.CertificateAuthority.AccountID)
|
|
||||||
}
|
|
||||||
return []*survey.Question{
|
|
||||||
{
|
|
||||||
Name: "AccountID",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "AWS Account ID:",
|
|
||||||
Default: defaultAccountID,
|
|
||||||
Help: "12 Digit account ID. You could get this by running `aws sts get-caller-identity`",
|
|
||||||
},
|
|
||||||
Validate: survey.ComposeValidators(
|
|
||||||
survey.Required,
|
|
||||||
validateInt,
|
|
||||||
survey.MaxLength(12),
|
|
||||||
survey.MinLength(12),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Regions",
|
|
||||||
Prompt: &survey.MultiSelect{
|
|
||||||
Message: "AWS Region:",
|
|
||||||
Default: config.CertificateAuthority.Regions,
|
|
||||||
Help: "Select multiple regions for high availability. Each region gets it's own Lambda function and KMS key.",
|
|
||||||
Options: SupportedAwsRegions,
|
|
||||||
PageSize: 10,
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "FunctionName",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "Lambda Function Name:",
|
|
||||||
Help: "The sshrimp certificate authority lambda will have this name.",
|
|
||||||
Default: config.CertificateAuthority.FunctionName,
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "KeyAlias",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "KMS Key Alias:",
|
|
||||||
Help: "A name beginning with 'alias/' to easily refer to KMS keys in IAM policies and configuration files.",
|
|
||||||
Default: config.CertificateAuthority.KeyAlias,
|
|
||||||
},
|
|
||||||
Validate: survey.ComposeValidators(
|
|
||||||
survey.Required,
|
|
||||||
validateAlias,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "UsernameClaim",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "Username claim in JWT",
|
|
||||||
Help: "Which claim in the JWT should be used as the username. See https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims",
|
|
||||||
Default: config.CertificateAuthority.UsernameClaim,
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "UsernameRegex",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "Username regular expression",
|
|
||||||
Help: "A regular expression to validate the username present in the identity token. The first matching group will be used as the username enforced in the certificate.",
|
|
||||||
Default: config.CertificateAuthority.UsernameRegex,
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "ForceCommandRegex",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "ForceCommand regular expression:",
|
|
||||||
Help: "A regular expression to validate the force command supplied by the user, but enforced in the certificate. See https://man.openbsd.org/sshd_config#ForceCommand",
|
|
||||||
Default: config.CertificateAuthority.ForceCommandRegex,
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "SourceAddressRegex",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "Source IP address regular expression",
|
|
||||||
Help: "A regular expression to validate the source IP address supplied by the user, but enforced in the certificate.",
|
|
||||||
Default: config.CertificateAuthority.SourceAddressRegex,
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "ValidAfterOffset",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "A time.now() offset for valid_after",
|
|
||||||
Help: "The amount to add to time.now() that the certificate will be valid FROM.",
|
|
||||||
Default: config.CertificateAuthority.ValidAfterOffset,
|
|
||||||
},
|
|
||||||
Validate: survey.ComposeValidators(
|
|
||||||
survey.Required,
|
|
||||||
validateDuration,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "ValidBeforeOffset",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "A time.now() offset for valid_before",
|
|
||||||
Help: "The amount to add to time.now() that the certificate will be valid TO.",
|
|
||||||
Default: config.CertificateAuthority.ValidBeforeOffset,
|
|
||||||
},
|
|
||||||
Validate: survey.ComposeValidators(
|
|
||||||
survey.Required,
|
|
||||||
validateDuration,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Extensions",
|
|
||||||
Prompt: &survey.MultiSelect{
|
|
||||||
Message: "Certificate extensions",
|
|
||||||
Help: "Extensions to be added to the certificate, see https://man.openbsd.org/ssh-keygen#CERTIFICATES",
|
|
||||||
Default: config.CertificateAuthority.Extensions,
|
|
||||||
Options: supportedExtensions,
|
|
||||||
PageSize: 10,
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func agentQuestions(config *SSHrimp) []*survey.Question {
|
|
||||||
return []*survey.Question{
|
|
||||||
{
|
|
||||||
Name: "ProviderURL",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "OpenIDConnect Provider URL:",
|
|
||||||
Default: config.Agent.ProviderURL,
|
|
||||||
Help: "Get this from your OIDC provider. For example Google's is https://accounts.google.com.",
|
|
||||||
},
|
|
||||||
Validate: survey.ComposeValidators(survey.Required, validateURL),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "ClientID",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "OpenIDConnect Client ID:",
|
|
||||||
Default: config.Agent.ClientID,
|
|
||||||
Help: "Get this from your OIDC provider. For example Google uses the format 1234-0a1b2bc3.apps.googleusercontent.com",
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "ClientSecret",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "OpenIDConnect Client Secret (only if required):",
|
|
||||||
Default: config.Agent.ClientSecret,
|
|
||||||
Help: "Google requires the Client Secret even when using PKCE. Most OpenIDConnect provdiders don't. Read more about PKCE: https://tools.ietf.org/html/rfc7636",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Socket",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "sshrimp-agent socket:",
|
|
||||||
Default: config.Agent.Socket,
|
|
||||||
Help: "Path of the socket for the sshrimp-agent to listen on. Create a unique one for each instance of sshrimp-agent.",
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func browserCommandQuestions(config *SSHrimp) []*survey.Question {
|
|
||||||
return []*survey.Question{
|
|
||||||
{
|
|
||||||
Name: "BrowserCommand",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "Command to open a browser:",
|
|
||||||
Default: shellquote.Join(config.Agent.BrowserCommand...),
|
|
||||||
Help: "Optionally {} will be substituted with the URL to open.",
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func configFileQuestions(configPath string) []*survey.Question {
|
|
||||||
return []*survey.Question{
|
|
||||||
{
|
|
||||||
Name: "ConfigPath",
|
|
||||||
Prompt: &survey.Input{
|
|
||||||
Message: "File path to write the new config:",
|
|
||||||
Default: configPath,
|
|
||||||
Help: "Set environment variable SSHRIMP_CONFIG to this path if different from ./sshrimp.toml",
|
|
||||||
},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SSHrimp) Read(configPath string) error {
|
func (c *SSHrimp) Read(configPath string) error {
|
||||||
_, err := toml.DecodeFile(configPath, c)
|
_, err := toml.DecodeFile(configPath, c)
|
||||||
return err
|
return err
|
||||||
@@ -403,56 +157,3 @@ func (c *SSHrimp) Write(configPath string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wizard launches a interactive question/answer terminal prompt to create a config file
|
|
||||||
func Wizard(configPath string, config *SSHrimp) (string, error) {
|
|
||||||
|
|
||||||
// Create a new config that doesn't have any default values, otherwise survey appends to the defaults.
|
|
||||||
newConfig := NewSSHrimp()
|
|
||||||
|
|
||||||
if err := survey.Ask(certificateAuthorityQuestions(config), &newConfig.CertificateAuthority); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := survey.Ask(agentQuestions(config), &newConfig.Agent); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask BrowserCommand separately so we can store it as a []string, currently not supported by survey.
|
|
||||||
var browserCommand string
|
|
||||||
if err := survey.Ask(browserCommandQuestions(config), &browserCommand); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// Split the command by sh rules using shellquote. The command is stored as a slice of arguments.
|
|
||||||
words, err := shellquote.Split(browserCommand)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
newConfig.Agent.BrowserCommand = words
|
|
||||||
|
|
||||||
// Confirm config file path, and keep prompting if exists and user chooses not to overwrite
|
|
||||||
var overwriteIfExists = false
|
|
||||||
for !overwriteIfExists {
|
|
||||||
if err := survey.Ask(configFileQuestions(configPath), &configPath); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); err == nil {
|
|
||||||
// File exists, confirm to be overwritten
|
|
||||||
if err := survey.AskOne(&survey.Confirm{
|
|
||||||
Message: "File exists, overwrite?",
|
|
||||||
Default: false,
|
|
||||||
}, &overwriteIfExists); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// File doesn't exist, break and save the file
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the new configuration to a file
|
|
||||||
newConfig.Write(configPath)
|
|
||||||
|
|
||||||
return configPath, nil
|
|
||||||
}
|
|
||||||
|
|||||||
39
internal/http/client.go
Normal file
39
internal/http/client.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
http.RoundTripper
|
||||||
|
UserAgent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if req.Header.Get("User-Agent") == "" {
|
||||||
|
req.Header.Set("User-Agent", t.UserAgent)
|
||||||
|
}
|
||||||
|
if t.RoundTripper == nil {
|
||||||
|
d := &net.Dialer{
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}
|
||||||
|
t.RoundTripper = &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: d.DialContext,
|
||||||
|
ForceAttemptHTTP2: false,
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 2 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t.RoundTripper.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Client = &http.Client{
|
||||||
|
Transport: &Transport{UserAgent: "sshrimp-agent"},
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
@@ -2,23 +2,28 @@ package identity
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
"github.com/coreos/go-oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Identity holds information required to verify an OIDC identity token
|
// Identity holds information required to verify an OIDC identity token
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
verifier *oidc.IDTokenVerifier
|
verifier *oidc.IDTokenVerifier
|
||||||
usernameRE *regexp.Regexp
|
usernameREs []*regexp.Regexp
|
||||||
usernameClaim string
|
usernameClaims []string
|
||||||
|
log *logrus.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIdentity return a new Identity, with default values and oidc proivder information populated
|
// NewIdentity return a new Identity, with default values and oidc proivder information populated
|
||||||
func NewIdentity(c *config.SSHrimp) (*Identity, error) {
|
func NewIdentity(log *logrus.Entry, c *config.SSHrimp) (*Identity, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
provider, err := oidc.NewProvider(ctx, c.Agent.ProviderURL)
|
provider, err := oidc.NewProvider(ctx, c.Agent.ProviderURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -30,38 +35,107 @@ func NewIdentity(c *config.SSHrimp) (*Identity, error) {
|
|||||||
SupportedSigningAlgs: []string{"RS256"},
|
SupportedSigningAlgs: []string{"RS256"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regexes := make([]*regexp.Regexp, 0, len(c.CertificateAuthority.UsernameRegexs))
|
||||||
|
for _, regex := range c.CertificateAuthority.UsernameRegexs {
|
||||||
|
regexes = append(regexes, regexp.MustCompile(regex))
|
||||||
|
}
|
||||||
|
|
||||||
return &Identity{
|
return &Identity{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
verifier: provider.Verifier(oidcConfig),
|
verifier: provider.Verifier(oidcConfig),
|
||||||
usernameRE: regexp.MustCompile(c.CertificateAuthority.UsernameRegex),
|
usernameREs: regexes,
|
||||||
usernameClaim: c.CertificateAuthority.UsernameClaim,
|
usernameClaims: c.CertificateAuthority.UsernameClaims,
|
||||||
|
log: log,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate an identity token
|
// Validate an identity token
|
||||||
func (i *Identity) Validate(token string) (string, error) {
|
func (i *Identity) Validate(token string) ([]string, error) {
|
||||||
|
|
||||||
idToken, err := i.verifier.Verify(i.ctx, token)
|
idToken, err := i.verifier.Verify(i.ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("failed to verify identity token: " + err.Error())
|
return nil, errors.New("failed to verify identity token: " + err.Error())
|
||||||
}
|
}
|
||||||
|
return i.getUsernames(idToken)
|
||||||
|
}
|
||||||
|
|
||||||
var claims map[string]interface{}
|
func (i *Identity) getUsernames(idToken *oidc.IDToken) ([]string, error) {
|
||||||
|
var claims map[string]any
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
return "", errors.New("failed to parse claims: " + err.Error())
|
return nil, errors.New("failed to parse claims: " + err.Error())
|
||||||
}
|
}
|
||||||
|
usernames := make([]string, 0, len(i.usernameClaims))
|
||||||
|
for idx, claim := range i.usernameClaims {
|
||||||
|
|
||||||
claimedUsername, ok := claims[i.usernameClaim].(string)
|
claimedUsernames := i.getClaim(claim, claims)
|
||||||
if !ok {
|
|
||||||
return "", errors.New("configured username claim not in identity token")
|
if len(claimedUsernames) == 0 {
|
||||||
|
i.log.Errorf("Did not find a username using: getClaim(%#v, %#v)", claim, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx < len(i.usernameREs) {
|
||||||
|
for _, name := range claimedUsernames {
|
||||||
|
usernames = append(usernames, parseUsername(name, i.usernameREs[idx]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
usernames = append(usernames, claimedUsernames...)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
i.log.Infof("Adding usernames: %v", usernames)
|
||||||
return i.parseUsername(claimedUsername)
|
if len(usernames) < 1 {
|
||||||
|
return nil, errors.New("configured username claim not in identity token")
|
||||||
|
}
|
||||||
|
return usernames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Identity) parseUsername(username string) (string, error) {
|
func parseUsername(username string, re *regexp.Regexp) string {
|
||||||
if match := i.usernameRE.FindStringSubmatch(username); match != nil {
|
if match := re.FindStringSubmatch(username); match != nil {
|
||||||
return match[1], nil
|
return match[1]
|
||||||
}
|
}
|
||||||
return "", errors.New("unable to parse username from claim")
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Identity) getClaim(claim string, claims map[string]any) []string {
|
||||||
|
usernames := make([]string, 0, 2)
|
||||||
|
parts := strings.Split(claim, ".")
|
||||||
|
f:
|
||||||
|
for idx, part := range parts {
|
||||||
|
switch v := claims[part].(type) {
|
||||||
|
case map[string]any:
|
||||||
|
claims = v
|
||||||
|
case []map[string]string:
|
||||||
|
for _, claimItem := range v {
|
||||||
|
name, ok := claimItem[parts[idx+1]]
|
||||||
|
if ok {
|
||||||
|
usernames = append(usernames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break f
|
||||||
|
case []any:
|
||||||
|
for _, value := range v {
|
||||||
|
if name, ok := value.(string); ok {
|
||||||
|
usernames = append(usernames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break f
|
||||||
|
case string:
|
||||||
|
usernames = append(usernames, v)
|
||||||
|
default:
|
||||||
|
break f
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return i.base64Decode(usernames)
|
||||||
|
}
|
||||||
|
func (i *Identity) base64Decode(names []string) []string {
|
||||||
|
for idx, name := range names {
|
||||||
|
i.log.Debugf("Attempting to decode %q as base64\n", name)
|
||||||
|
decoded, err := base64.RawURLEncoding.DecodeString(name)
|
||||||
|
if err == nil && utf8.Valid(decoded) {
|
||||||
|
names[idx] = string(decoded)
|
||||||
|
i.log.Debugf("Successfully decoded %q as base64\n", names[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package signer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sshAlgorithmSigner struct {
|
|
||||||
algorithm string
|
|
||||||
signer ssh.AlgorithmSigner
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey returns the wrapped signers public key
|
|
||||||
func (s *sshAlgorithmSigner) PublicKey() ssh.PublicKey {
|
|
||||||
return s.signer.PublicKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign uses the correct algorithm to sign the certificate
|
|
||||||
func (s *sshAlgorithmSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
|
||||||
return s.signer.SignWithAlgorithm(rand, data, s.algorithm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAlgorithmSignerFromSigner returns a ssh.Signer with a different default algorithm.
|
|
||||||
// Waiting for upstream changes to x/crypto/ssh, see: https://github.com/golang/go/issues/36261
|
|
||||||
func NewAlgorithmSignerFromSigner(signer crypto.Signer, algorithm string) (ssh.Signer, error) {
|
|
||||||
sshSigner, err := ssh.NewSignerFromSigner(signer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
algorithmSigner, ok := sshSigner.(ssh.AlgorithmSigner)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unable to cast to ssh.AlgorithmSigner")
|
|
||||||
}
|
|
||||||
s := sshAlgorithmSigner{
|
|
||||||
signer: algorithmSigner,
|
|
||||||
algorithm: algorithm,
|
|
||||||
}
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package signer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/kms"
|
|
||||||
"github.com/aws/aws-sdk-go/service/kms/kmsiface"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KMSSigner an AWS asymetric crypto signer
|
|
||||||
type AWSSigner struct {
|
|
||||||
crypto.Signer
|
|
||||||
client kmsiface.KMSAPI
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewKMSSigner return a new instsance of AWSSigner
|
|
||||||
func NewAWSSigner(key string) *AWSSigner {
|
|
||||||
|
|
||||||
sess := session.Must(session.NewSession())
|
|
||||||
|
|
||||||
return &AWSSigner{
|
|
||||||
key: key,
|
|
||||||
client: kms.New(sess),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public returns the public key from KMS
|
|
||||||
func (s *AWSSigner) Public() crypto.PublicKey {
|
|
||||||
|
|
||||||
response, err := s.client.GetPublicKey(&kms.GetPublicKeyInput{
|
|
||||||
KeyId: &s.key,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKey, err := x509.ParsePKIXPublicKey(response.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return publicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign a digest with the private key in KMS
|
|
||||||
func (s *AWSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
||||||
|
|
||||||
response, err := s.client.Sign(&kms.SignInput{
|
|
||||||
KeyId: &s.key,
|
|
||||||
Message: digest,
|
|
||||||
MessageType: aws.String(kms.MessageTypeDigest),
|
|
||||||
SigningAlgorithm: aws.String(kms.SigningAlgorithmSpecRsassaPkcs1V15Sha256),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.Signature, nil
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package signer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
kms "cloud.google.com/go/kms/apiv1"
|
|
||||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GCPSigner a GCP asymetric crypto signer
|
|
||||||
type GCPSigner struct {
|
|
||||||
crypto.Signer
|
|
||||||
ctx context.Context
|
|
||||||
client *kms.KeyManagementClient
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGCPSSigner return a new instsance of NewGCPSSigner
|
|
||||||
func NewGCPSSigner(key string) *GCPSigner {
|
|
||||||
ctx := context.Background()
|
|
||||||
c, err := kms.NewKeyManagementClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GCPSigner{
|
|
||||||
ctx: ctx,
|
|
||||||
client: c,
|
|
||||||
key: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public returns the public key from KMS
|
|
||||||
func (s *GCPSigner) Public() crypto.PublicKey {
|
|
||||||
response, err := s.client.GetPublicKey(s.ctx, &kmspb.GetPublicKeyRequest{
|
|
||||||
Name: s.key,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch response.GetAlgorithm() {
|
|
||||||
case kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256,
|
|
||||||
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256,
|
|
||||||
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
|
||||||
kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512:
|
|
||||||
// awesome
|
|
||||||
default:
|
|
||||||
fmt.Println("crypto key has the wrong algorithm, must be rsa with PKCS1 padding")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pubPem := response.GetPem()
|
|
||||||
// pubAlg := response.GetAlgorithm()
|
|
||||||
pemBlock, _ := pem.Decode([]byte(pubPem))
|
|
||||||
|
|
||||||
publicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return publicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign a digest with the private key in KMS
|
|
||||||
func (s *GCPSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
||||||
var dig *kmspb.Digest = &kmspb.Digest{}
|
|
||||||
switch opts {
|
|
||||||
case crypto.SHA256:
|
|
||||||
dig.Digest = &kmspb.Digest_Sha256{
|
|
||||||
Sha256: digest,
|
|
||||||
}
|
|
||||||
case crypto.SHA384:
|
|
||||||
dig.Digest = &kmspb.Digest_Sha384{
|
|
||||||
Sha384: digest,
|
|
||||||
}
|
|
||||||
case crypto.SHA512:
|
|
||||||
dig.Digest = &kmspb.Digest_Sha512{
|
|
||||||
Sha512: digest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := s.client.AsymmetricSign(s.ctx, &kmspb.AsymmetricSignRequest{
|
|
||||||
Name: s.key,
|
|
||||||
Digest: dig,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sig := response.GetSignature()
|
|
||||||
pubKey := s.Public()
|
|
||||||
rKey := pubKey.(*rsa.PublicKey)
|
|
||||||
err = rsa.VerifyPKCS1v15(rKey, crypto.SHA256, digest, sig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
@@ -5,21 +5,18 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/identity"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/http"
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/identity"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/lambda"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@@ -42,21 +39,17 @@ type SSHrimpEvent struct {
|
|||||||
ForceCommand string `json:"forcecommand"`
|
ForceCommand string `json:"forcecommand"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignCertificateAllRegions iterate through each configured region if there is an error signing the certificate
|
// SignCertificateAllURLs iterate through each configured url if there is an error signing the certificate
|
||||||
func SignCertificateAllRegions(publicKey ssh.PublicKey, token string, forceCommand string, c *config.SSHrimp) (*ssh.Certificate, error) {
|
func SignCertificateAllURLs(publicKey ssh.PublicKey, token string, forceCommand string, urls []string) (*ssh.Certificate, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err = fmt.Errorf("no urls found to sign certificate")
|
||||||
cert *ssh.Certificate
|
cert *ssh.Certificate
|
||||||
)
|
)
|
||||||
|
|
||||||
// Try each configured region before exiting if there is an error
|
// Try each configured url before exiting if there is an error
|
||||||
|
Log.Logger.Tracef("Attempting to sign cert with urls %v", urls)
|
||||||
for _, region := range c.CertificateAuthority.Regions {
|
for _, url := range urls {
|
||||||
if i := sort.SearchStrings(config.SupportedAwsRegions, region); i < len(config.SupportedAwsRegions) && config.SupportedAwsRegions[i] == region {
|
cert, err = SignCertificate(publicKey, token, forceCommand, url)
|
||||||
cert, err = SignCertificateAWS(publicKey, token, forceCommand, region, c)
|
|
||||||
} else if i := sort.SearchStrings(config.SupportedGcpRegions, region); i < len(config.SupportedGcpRegions) && config.SupportedGcpRegions[i] == region {
|
|
||||||
cert, err = SignCertificateGCP(publicKey, token, forceCommand, region, c)
|
|
||||||
}
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
@@ -64,8 +57,8 @@ func SignCertificateAllRegions(publicKey ssh.PublicKey, token string, forceComma
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignCertificateGCP given a public key, identity token and forceCommand, invoke the sshrimp-ca GCP function
|
// SignCertificate given a public key, identity token and forceCommand, invoke the sshrimp-ca GCP function
|
||||||
func SignCertificateGCP(publicKey ssh.PublicKey, token string, forceCommand string, region string, c *config.SSHrimp) (*ssh.Certificate, error) {
|
func SignCertificate(publicKey ssh.PublicKey, token string, forceCommand string, url string) (*ssh.Certificate, error) {
|
||||||
// Setup the JSON payload for the SSHrimp CA
|
// Setup the JSON payload for the SSHrimp CA
|
||||||
payload, err := json.Marshal(SSHrimpEvent{
|
payload, err := json.Marshal(SSHrimpEvent{
|
||||||
PublicKey: string(ssh.MarshalAuthorizedKey(publicKey)),
|
PublicKey: string(ssh.MarshalAuthorizedKey(publicKey)),
|
||||||
@@ -76,20 +69,23 @@ func SignCertificateGCP(publicKey ssh.PublicKey, token string, forceCommand stri
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := http.Post(fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", region, c.CertificateAuthority.Project, c.CertificateAuthority.FunctionName), "application/json", bytes.NewReader(payload))
|
Log.Logger.Tracef("Posting to url %s", url)
|
||||||
|
result, err := http.Client.Post(url, "application/json", bytes.NewReader(payload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "http post failed: "+err.Error())
|
return nil, fmt.Errorf("http post failed: %w", err)
|
||||||
}
|
}
|
||||||
resbody, err := ioutil.ReadAll(result.Body)
|
Log.Logger.Tracef("Reading body length: %d", result.ContentLength)
|
||||||
|
resbody, err := io.ReadAll(result.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to retrieve the response from sshrimp-ca")
|
return nil, fmt.Errorf("failed to retrieve the response from sshrimp-ca: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the result form the lambda to extract the certificate
|
// Parse the result form the lambda to extract the certificate
|
||||||
sshrimpResult := SSHrimpResult{}
|
sshrimpResult := SSHrimpResult{}
|
||||||
|
Log.Logger.Tracef("parsing result: %v", string(resbody))
|
||||||
err = json.Unmarshal(resbody, &sshrimpResult)
|
err = json.Unmarshal(resbody, &sshrimpResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse json response from sshrimp-ca.: "+string(resbody))
|
return nil, fmt.Errorf("failed to parse json response from sshrimp-ca: %w: %v", err, string(resbody))
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.StatusCode != 200 {
|
if result.StatusCode != 200 {
|
||||||
@@ -103,63 +99,14 @@ func SignCertificateGCP(publicKey ssh.PublicKey, token string, forceCommand stri
|
|||||||
|
|
||||||
// Parse the certificate received by sshrimp-ca
|
// Parse the certificate received by sshrimp-ca
|
||||||
cert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshrimpResult.Certificate))
|
cert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshrimpResult.Certificate))
|
||||||
|
Log.Logger.Tracef("parsing cert: %v", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return cert.(*ssh.Certificate), nil
|
return cert.(*ssh.Certificate), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignCertificateAWS given a public key, identity token and forceCommand, invoke the sshrimp-ca lambda function
|
func ValidateRequest(log *logrus.Entry, event SSHrimpEvent, c *config.SSHrimp, requestID string, ca ssh.PublicKey) (ssh.Certificate, error) {
|
||||||
func SignCertificateAWS(publicKey ssh.PublicKey, token string, forceCommand string, region string, c *config.SSHrimp) (*ssh.Certificate, error) {
|
|
||||||
// Create a lambdaService using the new temporary credentials for the role
|
|
||||||
session := session.Must(session.NewSession(&aws.Config{
|
|
||||||
Region: aws.String(region),
|
|
||||||
}))
|
|
||||||
lambdaService := lambda.New(session)
|
|
||||||
|
|
||||||
// Setup the JSON payload for the SSHrimp CA
|
|
||||||
payload, err := json.Marshal(SSHrimpEvent{
|
|
||||||
PublicKey: string(ssh.MarshalAuthorizedKey(publicKey)),
|
|
||||||
Token: token,
|
|
||||||
ForceCommand: forceCommand,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke the SSHrimp lambda
|
|
||||||
result, err := lambdaService.Invoke(&lambda.InvokeInput{
|
|
||||||
FunctionName: aws.String(c.CertificateAuthority.FunctionName),
|
|
||||||
Payload: payload,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if *result.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("sshrimp returned status code %d", *result.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the result form the lambda to extract the certificate
|
|
||||||
sshrimpResult := SSHrimpResult{}
|
|
||||||
err = json.Unmarshal(result.Payload, &sshrimpResult)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to parse json response from sshrimp-ca")
|
|
||||||
}
|
|
||||||
|
|
||||||
// These error types and messages can also come from the aws-sdk-go lambda handler
|
|
||||||
if sshrimpResult.ErrorType != "" || sshrimpResult.ErrorMessage != "" {
|
|
||||||
return nil, fmt.Errorf("%s: %s", sshrimpResult.ErrorType, sshrimpResult.ErrorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the certificate received by sshrimp-ca
|
|
||||||
cert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshrimpResult.Certificate))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cert.(*ssh.Certificate), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateRequest(event SSHrimpEvent, c *config.SSHrimp, requestID string, functionID string) (ssh.Certificate, error) {
|
|
||||||
// Validate the user supplied public key
|
// Validate the user supplied public key
|
||||||
publicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(event.PublicKey))
|
publicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(event.PublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -167,8 +114,11 @@ func ValidateRequest(event SSHrimpEvent, c *config.SSHrimp, requestID string, fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate the user supplied identity token with the loaded configuration
|
// Validate the user supplied identity token with the loaded configuration
|
||||||
i, _ := identity.NewIdentity(c)
|
i, err := identity.NewIdentity(log, c)
|
||||||
username, err := i.Validate(event.Token)
|
if err != nil {
|
||||||
|
return ssh.Certificate{}, err
|
||||||
|
}
|
||||||
|
usernames, err := i.Validate(event.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ssh.Certificate{}, err
|
return ssh.Certificate{}, err
|
||||||
}
|
}
|
||||||
@@ -191,12 +141,12 @@ func ValidateRequest(event SSHrimpEvent, c *config.SSHrimp, requestID string, fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate a random nonce for the certificate
|
// Generate a random nonce for the certificate
|
||||||
bytes := make([]byte, 32)
|
nonceHex := make([]byte, 32)
|
||||||
nonce := make([]byte, len(bytes)*2)
|
nonce := make([]byte, len(nonceHex)*2)
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
if _, err := rand.Read(nonceHex); err != nil {
|
||||||
return ssh.Certificate{}, err
|
return ssh.Certificate{}, err
|
||||||
}
|
}
|
||||||
hex.Encode(nonce, bytes)
|
hex.Encode(nonce, nonceHex)
|
||||||
|
|
||||||
// Generate a random serial number
|
// Generate a random serial number
|
||||||
serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
serial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
||||||
@@ -227,24 +177,22 @@ func ValidateRequest(event SSHrimpEvent, c *config.SSHrimp, requestID string, fu
|
|||||||
// https://github.com/Netflix/bless
|
// https://github.com/Netflix/bless
|
||||||
keyID := fmt.Sprintf("request[%s] for[%s] from[%s] command[%s] ssh_key[%s] ca[%s] valid_to[%s]",
|
keyID := fmt.Sprintf("request[%s] for[%s] from[%s] command[%s] ssh_key[%s] ca[%s] valid_to[%s]",
|
||||||
requestID,
|
requestID,
|
||||||
username,
|
strings.Join(usernames, ", "),
|
||||||
event.SourceAddress,
|
event.SourceAddress,
|
||||||
event.ForceCommand,
|
event.ForceCommand,
|
||||||
ssh.FingerprintSHA256(publicKey),
|
ssh.FingerprintSHA256(publicKey),
|
||||||
functionID,
|
ssh.FingerprintSHA256(ca),
|
||||||
validBefore.Format("2006/01/02 15:04:05"),
|
validBefore.Format("2006/01/02 15:04:05"),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the certificate struct with all our configured alues
|
// Create the certificate struct with all our configured alues
|
||||||
certificate := ssh.Certificate{
|
certificate := ssh.Certificate{
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
Key: publicKey,
|
Key: publicKey,
|
||||||
Serial: serial.Uint64(),
|
Serial: serial.Uint64(),
|
||||||
CertType: ssh.UserCert,
|
CertType: ssh.UserCert,
|
||||||
KeyId: keyID,
|
KeyId: keyID,
|
||||||
ValidPrincipals: []string{
|
ValidPrincipals: usernames,
|
||||||
username,
|
|
||||||
},
|
|
||||||
Permissions: ssh.Permissions{
|
Permissions: ssh.Permissions{
|
||||||
CriticalOptions: criticalOptions,
|
CriticalOptions: criticalOptions,
|
||||||
Extensions: extensions,
|
Extensions: extensions,
|
||||||
|
|||||||
161
internal/sshrimpagent/auth.go
Normal file
161
internal/sshrimpagent/auth.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package sshrimpagent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
|
sshrimp_http "gitea.narnian.us/lordwelch/sshrimp/internal/http"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/zitadel/oidc/pkg/client/rp"
|
||||||
|
httphelper "github.com/zitadel/oidc/pkg/http"
|
||||||
|
"github.com/zitadel/oidc/pkg/oidc"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hashKey = []byte(uuid.New().String())[:16]
|
||||||
|
|
||||||
|
type OidcClient struct {
|
||||||
|
ListenAddress string
|
||||||
|
*http.Server
|
||||||
|
oidcMux *http.ServeMux
|
||||||
|
OIDCToken chan *oidc.Tokens
|
||||||
|
Certificate *ssh.Certificate
|
||||||
|
*config.SSHrimp
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOIDCClient(c *config.SSHrimp) (*OidcClient, error) {
|
||||||
|
if len(c.Agent.Scopes) < 1 {
|
||||||
|
c.Agent.Scopes = []string{"openid", "email", "profile"}
|
||||||
|
}
|
||||||
|
if !slices.Contains(c.Agent.Scopes, "openid") {
|
||||||
|
c.Agent.Scopes = append([]string{"scopes"}, c.Agent.Scopes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := make(chan *oidc.Tokens)
|
||||||
|
|
||||||
|
oidcMux := http.NewServeMux()
|
||||||
|
return &OidcClient{
|
||||||
|
oidcMux: oidcMux,
|
||||||
|
Server: &http.Server{
|
||||||
|
Addr: fmt.Sprintf("localhost:%d", c.Agent.Port),
|
||||||
|
Handler: oidcMux,
|
||||||
|
ReadTimeout: time.Minute / 2,
|
||||||
|
ReadHeaderTimeout: time.Minute / 2,
|
||||||
|
WriteTimeout: time.Minute / 2,
|
||||||
|
IdleTimeout: time.Minute / 2,
|
||||||
|
},
|
||||||
|
OIDCToken: token,
|
||||||
|
Certificate: &ssh.Certificate{},
|
||||||
|
SSHrimp: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OidcClient) baseURI() url.URL {
|
||||||
|
return url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: o.Addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OidcClient) ListenAndServe() error {
|
||||||
|
ln, err := net.Listen("tcp", o.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.Addr = ln.Addr().String()
|
||||||
|
if err = o.setupHandlers(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return o.Serve(ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OidcClient) setupHandlers() error {
|
||||||
|
redirectURI := o.baseURI()
|
||||||
|
redirectURI.Path = "/auth/callback"
|
||||||
|
successURI := o.baseURI()
|
||||||
|
successURI.Path = "/success"
|
||||||
|
var CAKey []byte
|
||||||
|
resp, err := sshrimp_http.Client.Get(o.Agent.CAUrls[0])
|
||||||
|
if err == nil && resp.Header.Get("Content-Type") == "text/x-ssh-public-key" {
|
||||||
|
CAKey, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
CAKey = []byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieHandler := httphelper.NewCookieHandler(hashKey, nil)
|
||||||
|
|
||||||
|
options := []rp.Option{
|
||||||
|
rp.WithCookieHandler(cookieHandler),
|
||||||
|
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
|
||||||
|
rp.WithHTTPClient(sshrimp_http.Client),
|
||||||
|
}
|
||||||
|
options = append(options, rp.WithPKCE(cookieHandler))
|
||||||
|
if o.Agent.KeyPath != "" {
|
||||||
|
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyPath(o.Agent.KeyPath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := rp.NewRelyingPartyOIDC(o.Agent.ProviderURL, o.Agent.ClientID, o.Agent.ClientSecret, redirectURI.String(), o.Agent.Scopes, options...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate some state (representing the state of the user in your application,
|
||||||
|
// e.g. the page where he was before sending him to login
|
||||||
|
state := func() string {
|
||||||
|
return uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the AuthURLHandler at your preferred path
|
||||||
|
// the AuthURLHandler creates the auth request and redirects the user to the auth server
|
||||||
|
// including state handling with secure cookie and the possibility to use PKCE
|
||||||
|
o.oidcMux.Handle("/login", rp.AuthURLHandler(state, provider))
|
||||||
|
o.oidcMux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if o.Certificate != nil && o.Certificate.SignatureKey != nil {
|
||||||
|
key := ssh.MarshalAuthorizedKey(o.Certificate.SignatureKey)
|
||||||
|
if len(CAKey) < 3 {
|
||||||
|
CAKey = key
|
||||||
|
}
|
||||||
|
if !slices.Equal(key, CAKey) {
|
||||||
|
Log.Errorf("Certificate Authority key has changed from %#v to %#v", string(CAKey), string(key))
|
||||||
|
fmt.Fprintf(w, "\n\nCertificate Authority key has changed from \n%#v\nto \n%#v", string(CAKey), string(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "The SSH CA currently in use is:\n%s", CAKey)
|
||||||
|
Log.Printf("The SSH CA currently in use is:\n%s", CAKey)
|
||||||
|
}))
|
||||||
|
o.oidcMux.Handle(successURI.Path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintln(w, "Return to the CLI.")
|
||||||
|
if o.Certificate != nil && o.Certificate.SignatureKey != nil {
|
||||||
|
key := ssh.MarshalAuthorizedKey(o.Certificate.SignatureKey)
|
||||||
|
if len(CAKey) < 3 {
|
||||||
|
CAKey = key
|
||||||
|
}
|
||||||
|
if !slices.Equal(key, CAKey) {
|
||||||
|
Log.Errorf("Certificate Authority key has changed from %#v to %#v", string(CAKey), string(key))
|
||||||
|
fmt.Fprintf(w, "\n\nCertificate Authority key has changed from \n%#v\nto \n%#v", string(CAKey), string(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "The SSH CA currently in use is: %s", CAKey)
|
||||||
|
Log.Printf("The SSH CA currently in use is:\n%s", CAKey)
|
||||||
|
}))
|
||||||
|
|
||||||
|
marshalUserinfo := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens, state string, rp rp.RelyingParty) {
|
||||||
|
o.OIDCToken <- tokens
|
||||||
|
w.Header().Add("location", successURI.String())
|
||||||
|
w.WriteHeader(301)
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the CodeExchangeHandler at the callbackPath
|
||||||
|
// the CodeExchangeHandler handles the auth response, creates the token request and calls the callback function
|
||||||
|
// with the returned tokens from the token endpoint
|
||||||
|
o.oidcMux.Handle(redirectURI.Path, rp.CodeExchangeHandler(marshalUserinfo, provider))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,12 +3,14 @@ package sshrimpagent
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/aws-oidc/provider"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/signer"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/signer"
|
"github.com/pkg/browser"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/zitadel/oidc/pkg/oidc"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/agent"
|
"golang.org/x/crypto/ssh/agent"
|
||||||
)
|
)
|
||||||
@@ -16,39 +18,64 @@ import (
|
|||||||
var Log *logrus.Entry
|
var Log *logrus.Entry
|
||||||
|
|
||||||
type sshrimpAgent struct {
|
type sshrimpAgent struct {
|
||||||
providerConfig provider.ProviderConfig
|
oidcClient *OidcClient
|
||||||
signer ssh.Signer
|
signer ssh.Signer
|
||||||
certificate *ssh.Certificate
|
certificate *ssh.Certificate
|
||||||
token *provider.OAuth2Token
|
token *oidc.Tokens
|
||||||
config *config.SSHrimp
|
config *config.SSHrimp
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSSHrimpAgent returns an agent.Agent capable of signing certificates with a SSHrimp Certificate Authority
|
// NewSSHrimpAgent returns an agent.Agent capable of signing certificates with a SSHrimp Certificate Authority
|
||||||
func NewSSHrimpAgent(c *config.SSHrimp, signer ssh.Signer) agent.Agent {
|
func NewSSHrimpAgent(c *config.SSHrimp, signer ssh.Signer) (agent.Agent, error) {
|
||||||
|
oidcClient, err := newOIDCClient(c)
|
||||||
providerConfig := provider.ProviderConfig{
|
if err != nil {
|
||||||
ClientID: c.Agent.ClientID,
|
return nil, err
|
||||||
ClientSecret: c.Agent.ClientSecret,
|
|
||||||
ProviderURL: c.Agent.ProviderURL,
|
|
||||||
PKCE: true,
|
|
||||||
Nonce: true,
|
|
||||||
AgentCommand: c.Agent.BrowserCommand,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if err = oidcClient.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
Log.Logger.Errorf("Server failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return &sshrimpAgent{
|
return &sshrimpAgent{
|
||||||
providerConfig: providerConfig,
|
oidcClient: oidcClient,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
certificate: &ssh.Certificate{},
|
certificate: &ssh.Certificate{},
|
||||||
token: &provider.OAuth2Token{},
|
token: nil,
|
||||||
config: c,
|
config: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authenticate authenticates a oidc token
|
||||||
|
func (r *sshrimpAgent) authenticate() error {
|
||||||
|
var err error
|
||||||
|
if r.token != nil {
|
||||||
|
err = oidc.CheckExpiration(r.token.IDTokenClaims, 0)
|
||||||
|
} else {
|
||||||
|
err = errors.New("no token provided")
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
Log.Debugln("Token is expired re-authenticating http://" + r.oidcClient.Addr + "/login")
|
||||||
|
_ = browser.OpenURL("http://" + r.oidcClient.Addr + "/login")
|
||||||
|
select {
|
||||||
|
case r.token = <-r.oidcClient.OIDCToken:
|
||||||
|
return nil
|
||||||
|
case <-time.After(30 * time.Second):
|
||||||
|
return errors.New("Timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAll clears the current certificate and identity token (including refresh token)
|
// RemoveAll clears the current certificate and identity token (including refresh token)
|
||||||
func (r *sshrimpAgent) RemoveAll() error {
|
func (r *sshrimpAgent) RemoveAll() error {
|
||||||
Log.Debugln("Removing identity token and certificate")
|
Log.Debugln("Removing identity token and certificate")
|
||||||
r.certificate = &ssh.Certificate{}
|
r.certificate = &ssh.Certificate{}
|
||||||
r.token = &provider.OAuth2Token{}
|
r.oidcClient.Certificate = r.certificate
|
||||||
|
r.token = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,19 +102,20 @@ func (r *sshrimpAgent) List() ([]*agent.Key, error) {
|
|||||||
if r.certificate.ValidBefore != uint64(ssh.CertTimeInfinity) && (time.Now().After(validEndDate) || validEndDate.Unix() < 0) {
|
if r.certificate.ValidBefore != uint64(ssh.CertTimeInfinity) && (time.Now().After(validEndDate) || validEndDate.Unix() < 0) {
|
||||||
Log.Traceln("Certificate has expired")
|
Log.Traceln("Certificate has expired")
|
||||||
Log.Traceln("authenticating token")
|
Log.Traceln("authenticating token")
|
||||||
err := r.providerConfig.Authenticate(r.token)
|
err := r.authenticate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log.Errorf("authenticating the token failed: %v", err)
|
Log.Errorf("authenticating the token failed: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Traceln("signing certificate")
|
Log.Traceln("signing certificate")
|
||||||
cert, err := signer.SignCertificateAllRegions(r.signer.PublicKey(), r.token.IDToken, "", r.config)
|
cert, err := signer.SignCertificateAllURLs(r.signer.PublicKey(), r.token.IDToken, "", r.config.Agent.CAUrls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log.Errorf("signing certificate failed: %v", err)
|
Log.Errorf("signing certificate failed: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r.certificate = cert
|
r.certificate = cert
|
||||||
|
r.oidcClient.Certificate = r.certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
var ids []*agent.Key
|
var ids []*agent.Key
|
||||||
@@ -123,7 +151,7 @@ func (r *sshrimpAgent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent
|
|||||||
if ok {
|
if ok {
|
||||||
if flags&agent.SignatureFlagRsaSha512 == agent.SignatureFlagRsaSha512 {
|
if flags&agent.SignatureFlagRsaSha512 == agent.SignatureFlagRsaSha512 {
|
||||||
Log.Traceln("sha 512 requested")
|
Log.Traceln("sha 512 requested")
|
||||||
s, err := sign.SignWithAlgorithm(rand.Reader, data, ssh.SigAlgoRSASHA2512)
|
s, err := sign.SignWithAlgorithm(rand.Reader, data, ssh.KeyAlgoRSASHA512)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
Log.Debugln("sha 512 available")
|
Log.Debugln("sha 512 available")
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -131,7 +159,7 @@ func (r *sshrimpAgent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent
|
|||||||
}
|
}
|
||||||
if flags&agent.SignatureFlagRsaSha256 == agent.SignatureFlagRsaSha256 {
|
if flags&agent.SignatureFlagRsaSha256 == agent.SignatureFlagRsaSha256 {
|
||||||
Log.Traceln("sha 256 requested")
|
Log.Traceln("sha 256 requested")
|
||||||
s, err := sign.SignWithAlgorithm(rand.Reader, data, ssh.SigAlgoRSASHA2256)
|
s, err := sign.SignWithAlgorithm(rand.Reader, data, ssh.KeyAlgoRSASHA256)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
Log.Debugln("sha 256 available")
|
Log.Debugln("sha 256 available")
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -141,6 +169,7 @@ func (r *sshrimpAgent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent
|
|||||||
Log.Traceln("signing data")
|
Log.Traceln("signing data")
|
||||||
return r.Sign(key, data)
|
return r.Sign(key, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *sshrimpAgent) Extension(extensionType string, contents []byte) ([]byte, error) {
|
func (r *sshrimpAgent) Extension(extensionType string, contents []byte) ([]byte, error) {
|
||||||
return nil, agent.ErrExtensionUnsupported
|
return nil, agent.ErrExtensionUnsupported
|
||||||
}
|
}
|
||||||
|
|||||||
38
magefile.go
38
magefile.go
@@ -1,38 +0,0 @@
|
|||||||
//+build mage
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/magefile/mage/mg"
|
|
||||||
|
|
||||||
// mage:import ca
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/tools/mage/ca"
|
|
||||||
// mage:import agent
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/tools/mage/agent"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Default = All
|
|
||||||
|
|
||||||
// Builds all the targets
|
|
||||||
func Build() {
|
|
||||||
mg.Deps(ca.Build, agent.Build)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all build output (except generated configuration files)
|
|
||||||
func Clean() {
|
|
||||||
mg.Deps(ca.Clean, agent.Clean)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build and deploy the ca and agent
|
|
||||||
func All() {
|
|
||||||
mg.Deps(agent.Build, ca.Package, ca.Generate)
|
|
||||||
|
|
||||||
if _, err := os.Stat("./terraform"); os.IsNotExist(err) {
|
|
||||||
fmt.Println("All done. Run `terraform init` then `terraform apply` to deploy.")
|
|
||||||
} else {
|
|
||||||
fmt.Println("All done. Run `terraform apply` to deploy.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
data "aws_iam_policy_document" "sshrimp_ca_assume_role" {
|
|
||||||
statement {
|
|
||||||
actions = ["sts:AssumeRole"]
|
|
||||||
|
|
||||||
principals {
|
|
||||||
type = "Service"
|
|
||||||
identifiers = ["lambda.amazonaws.com"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "aws_iam_policy_document" "sshrimp_ca" {
|
|
||||||
statement {
|
|
||||||
actions = [
|
|
||||||
"kms:Sign",
|
|
||||||
"kms:GetPublicKey"
|
|
||||||
]
|
|
||||||
resources = [
|
|
||||||
"${aws_kms_key.sshrimp_ca_private_key.arn}",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
statement {
|
|
||||||
actions = [
|
|
||||||
"logs:CreateLogGroup",
|
|
||||||
"logs:CreateLogStream",
|
|
||||||
"logs:PutLogEvents",
|
|
||||||
]
|
|
||||||
|
|
||||||
resources = [
|
|
||||||
"*",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
resource "aws_iam_role_policy" "sshrimp_ca" {
|
|
||||||
name = "sshrimp-ca-${data.aws_region.current.name}"
|
|
||||||
role = aws_iam_role.sshrimp_ca.id
|
|
||||||
policy = data.aws_iam_policy_document.sshrimp_ca.json
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_iam_role" "sshrimp_ca" {
|
|
||||||
name = "sshrimp-ca-${data.aws_region.current.name}"
|
|
||||||
assume_role_policy = data.aws_iam_policy_document.sshrimp_ca_assume_role.json
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
data "aws_iam_policy_document" "sshrimp_ca_private_key" {
|
|
||||||
// Allow the root account to administer the key, but not encrypt/decrypt/sign
|
|
||||||
statement {
|
|
||||||
effect = "Allow"
|
|
||||||
|
|
||||||
principals {
|
|
||||||
type = "AWS"
|
|
||||||
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
|
|
||||||
}
|
|
||||||
|
|
||||||
actions = [
|
|
||||||
"kms:CancelKeyDeletion",
|
|
||||||
"kms:Create*",
|
|
||||||
"kms:Delete*",
|
|
||||||
"kms:Describe*",
|
|
||||||
"kms:Disable*",
|
|
||||||
"kms:Enable*",
|
|
||||||
"kms:Get*",
|
|
||||||
"kms:List*",
|
|
||||||
"kms:Put*",
|
|
||||||
"kms:Revoke*",
|
|
||||||
"kms:ScheduleKeyDeletion",
|
|
||||||
"kms:TagResource",
|
|
||||||
"kms:UntagResource",
|
|
||||||
"kms:Update*",
|
|
||||||
]
|
|
||||||
|
|
||||||
resources = ["*"]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow the SSHrimp lambda to sign and get the public key
|
|
||||||
statement {
|
|
||||||
effect = "Allow"
|
|
||||||
|
|
||||||
principals {
|
|
||||||
type = "AWS"
|
|
||||||
identifiers = ["${aws_iam_role.sshrimp_ca.arn}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
actions = [
|
|
||||||
"kms:Sign",
|
|
||||||
"kms:GetPublicKey",
|
|
||||||
]
|
|
||||||
|
|
||||||
resources = ["*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
resource "aws_kms_key" "sshrimp_ca_private_key" {
|
|
||||||
description = "KMS key used to sign SSH certificates for the SSHrimp Certificate Authority"
|
|
||||||
deletion_window_in_days = 10
|
|
||||||
customer_master_key_spec = "RSA_4096"
|
|
||||||
key_usage = "SIGN_VERIFY"
|
|
||||||
policy = data.aws_iam_policy_document.sshrimp_ca_private_key.json
|
|
||||||
depends_on = [
|
|
||||||
aws_iam_role.sshrimp_ca,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_kms_alias" "sshrimp_ca_private_key" {
|
|
||||||
name = "alias/${var.key_alias}"
|
|
||||||
target_key_id = aws_kms_key.sshrimp_ca_private_key.key_id
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
resource "aws_lambda_function" "sshrimp_ca" {
|
|
||||||
function_name = var.lambda_name
|
|
||||||
filename = "sshrimp-ca.zip"
|
|
||||||
role = aws_iam_role.sshrimp_ca.arn
|
|
||||||
timeout = 120
|
|
||||||
memory_size = 512
|
|
||||||
description = "SSHrimp Certificate Authority"
|
|
||||||
handler = "sshrimp-ca"
|
|
||||||
runtime = "go1.x"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
data "aws_caller_identity" "current" {}
|
|
||||||
|
|
||||||
data "aws_region" "current" {}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
variable "lambda_name" {
|
|
||||||
type = string
|
|
||||||
default = "sshrimp"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "key_alias" {
|
|
||||||
type = string
|
|
||||||
default = "sshrimp"
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/magefile/mage/sh"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Build Builds the local ssh agent
|
|
||||||
func Build() error {
|
|
||||||
return sh.Run("go", "build", "./cmd/sshrimp-agent")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean Cleans the output files for sshrimp-agent
|
|
||||||
func Clean() error {
|
|
||||||
return sh.Rm("sshrimp-agent")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install Installs the sshrimp-agent
|
|
||||||
func Install() error {
|
|
||||||
return sh.Run("go", "install", "./cmd/sshrimp-agent")
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
package ca
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/kms"
|
|
||||||
"github.com/magefile/mage/mg"
|
|
||||||
"github.com/magefile/mage/sh"
|
|
||||||
"github.com/magefile/mage/target"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config Generate a sshrimp configuration file if it doesn't exsit
|
|
||||||
func Config() error {
|
|
||||||
c := config.NewSSHrimpWithDefaults()
|
|
||||||
|
|
||||||
// Read the existing config if it doesn't exist, generate a new one
|
|
||||||
if err := c.Read(config.GetPath()); err != nil {
|
|
||||||
configPath, err := config.Wizard(config.GetPath(), c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// If a different config path is chosen, use it for the rest of the build
|
|
||||||
os.Setenv("SSHRIMP_CONFIG", configPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build Builds the certificate authority
|
|
||||||
func Build() error {
|
|
||||||
env := map[string]string{
|
|
||||||
"GOOS": "linux",
|
|
||||||
}
|
|
||||||
return sh.RunWith(env, "go", "build", "./cmd/sshrimp-ca")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build Builds the GCP certificate authority
|
|
||||||
// It produces gcp.a for checking timestamps
|
|
||||||
func BuildGCP() error {
|
|
||||||
env := map[string]string{
|
|
||||||
"GOOS": "linux",
|
|
||||||
}
|
|
||||||
return sh.RunWith(env, "go", "build", "-o", "gcp.a", "./gcp")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package Packages the certificate authority files into a zip archive
|
|
||||||
func Package() error {
|
|
||||||
if modified, err := target.Path("sshrimp-ca", config.GetPath()); err == nil && !modified {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.Deps(Build, Config)
|
|
||||||
|
|
||||||
zipFile, err := os.Create("sshrimp-ca.zip")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer zipFile.Close()
|
|
||||||
|
|
||||||
if err := lambdaCreateArchive(zipFile, "sshrimp-ca", config.GetPath()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PackageGCP Packages the certificate authority files into a zip archive
|
|
||||||
func PackageGCP() error {
|
|
||||||
if modified, err := target.Path("gcp.a", config.GetPath()); err == nil && !modified {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.Deps(BuildGCP, Config)
|
|
||||||
|
|
||||||
zipFile, err := os.Create("sshrimp-ca-gcp.zip")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer zipFile.Close()
|
|
||||||
|
|
||||||
err = gcpCreateArchive(zipFile, []ZipFiles{
|
|
||||||
ZipFiles{Filename: "go.mod"},
|
|
||||||
{"gcp/gcp.go", "gcp.go"},
|
|
||||||
ZipFiles{Filename: "internal"},
|
|
||||||
ZipFiles{config.GetPath(), filepath.Base(config.GetPath())},
|
|
||||||
}...)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate Generates a CloudFormation template used to deploy the certificate authority
|
|
||||||
func Generate() error {
|
|
||||||
if modified, err := target.Path("sshrimp-ca.tf.json", config.GetPath()); err == nil && !modified {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mg.Deps(Config)
|
|
||||||
|
|
||||||
c := config.NewSSHrimp()
|
|
||||||
if err := c.Read(config.GetPath()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := generateTerraform(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ioutil.WriteFile("sshrimp-ca.tf.json", template, 0644)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys Get the public keys of all configured KMS keys in OpenSSH format
|
|
||||||
func Keys() error {
|
|
||||||
|
|
||||||
c := config.NewSSHrimp()
|
|
||||||
if err := c.Read(config.GetPath()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each configured region, get the public key from KMS and format it in an OpenSSH authorized_keys format
|
|
||||||
for _, region := range c.CertificateAuthority.Regions {
|
|
||||||
|
|
||||||
// Create a new session in the correct region
|
|
||||||
session := session.Must(session.NewSession(&aws.Config{
|
|
||||||
Region: aws.String(region),
|
|
||||||
}))
|
|
||||||
svc := kms.New(session)
|
|
||||||
|
|
||||||
// Get the public key from KMS
|
|
||||||
response, err := svc.GetPublicKey(&kms.GetPublicKeyInput{
|
|
||||||
KeyId: aws.String(c.CertificateAuthority.KeyAlias),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the public key from KMS
|
|
||||||
publicKey, err := x509.ParsePKIXPublicKey(response.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the public key into an SSH public key
|
|
||||||
sshPublicKey, err := ssh.NewPublicKey(publicKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the final string to output on stdout
|
|
||||||
authorizedKey := strings.TrimSuffix(string(ssh.MarshalAuthorizedKey(sshPublicKey)), "\n")
|
|
||||||
fmt.Printf("%s sshrimp-ca@%s\n", authorizedKey, region)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean Cleans the output files for sshrimp-ca
|
|
||||||
func Clean() error {
|
|
||||||
if err := sh.Rm("sshrimp-ca"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := sh.Rm("sshrimp-ca.tf.json"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := sh.Rm("sshrimp-ca.zip"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := sh.Rm("sshrimp-ca-gcp.zip"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := sh.Rm("gcp.a"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
package ca
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ZipFiles struct {
|
|
||||||
Filename, ZipPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packages the certificate authority lambda into a zip archive on writer
|
|
||||||
func lambdaCreateArchive(wr io.Writer, filename ...string) error {
|
|
||||||
|
|
||||||
archive := zip.NewWriter(wr)
|
|
||||||
defer archive.Close()
|
|
||||||
|
|
||||||
for _, path := range filename {
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := zip.FileInfoHeader(info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
writer, err := archive.CreateHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(writer, file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packages the certificate authority function into a GCP compatible zip archive on writer
|
|
||||||
func gcpCreateArchive(wr io.Writer, files ...ZipFiles) error {
|
|
||||||
|
|
||||||
archive := zip.NewWriter(wr)
|
|
||||||
defer archive.Close()
|
|
||||||
|
|
||||||
for _, fileinfo := range files {
|
|
||||||
info, err := os.Stat(fileinfo.Filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
err = zipDirectory(archive, fileinfo.Filename, fileinfo.ZipPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(fileinfo.ZipPath) == "" {
|
|
||||||
fileinfo.ZipPath = fileinfo.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := zip.FileInfoHeader(info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
header.Name = fileinfo.ZipPath
|
|
||||||
|
|
||||||
writer, err := archive.CreateHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(fileinfo.Filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(writer, file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func zipDirectory(archive *zip.Writer, dir, zipDir string) error {
|
|
||||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var zipPath string
|
|
||||||
if strings.TrimSpace(zipDir) == "" {
|
|
||||||
zipPath = path
|
|
||||||
} else {
|
|
||||||
zipPath = strings.Replace(path, dir, zipDir, 1)
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := zip.FileInfoHeader(info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
header.Name = zipPath
|
|
||||||
|
|
||||||
writer, err := archive.CreateHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(writer, file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package ca
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
|
||||||
"github.com/awslabs/goformation/v4/cloudformation"
|
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/iam"
|
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/kms"
|
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/lambda"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makePolicyDocument(statement map[string]interface{}) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": []interface{}{
|
|
||||||
statement,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeAssumeRolePolicyDocument(service string) map[string]interface{} {
|
|
||||||
return makePolicyDocument(map[string]interface{}{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Principal": map[string][]string{
|
|
||||||
"Service": []string{service},
|
|
||||||
},
|
|
||||||
"Action": []string{"sts:AssumeRole"},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTemplate(c *config.SSHrimp) ([]byte, error) {
|
|
||||||
|
|
||||||
// Create a new CloudFormation template
|
|
||||||
template := cloudformation.NewTemplate()
|
|
||||||
|
|
||||||
template.Resources["SSHrimpPrivateKey"] = &kms.Key{
|
|
||||||
Description: "SSHrimp Certificate Authority Private Key",
|
|
||||||
PendingWindowInDays: 7,
|
|
||||||
KeyUsage: "SIGN_VERIFY",
|
|
||||||
KeyPolicy: makePolicyDocument(map[string]interface{}{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Principal": map[string][]string{
|
|
||||||
"AWS": []string{
|
|
||||||
cloudformation.GetAtt("SSHrimpLambdaExecutionRole", "Arn"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Action": []string{
|
|
||||||
"kms:GetPublicKey",
|
|
||||||
"kms:Sign",
|
|
||||||
},
|
|
||||||
"Resource": cloudformation.GetAtt("SSHrimpLambda", "Arn"),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
template.Resources["SSHrimpLambdaExecutionRole"] = &iam.Role{
|
|
||||||
AssumeRolePolicyDocument: makeAssumeRolePolicyDocument("lambda.amazonaws.com"),
|
|
||||||
RoleName: "sshrimp-ca",
|
|
||||||
Policies: []iam.Role_Policy{
|
|
||||||
{
|
|
||||||
PolicyDocument: makePolicyDocument(map[string]interface{}{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": "kms:Sign",
|
|
||||||
"Resource": "*",
|
|
||||||
}),
|
|
||||||
PolicyName: "sshrimp-ca-lambda",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
template.Resources["SSHrimpLambda"] = &lambda.Function{
|
|
||||||
FunctionName: c.CertificateAuthority.FunctionName,
|
|
||||||
Description: "SSHrimp Certificate Authority",
|
|
||||||
Role: cloudformation.GetAtt("SSHrimpLambdaExecutionRole", "Arn"),
|
|
||||||
Handler: "sshrimp-ca",
|
|
||||||
MemorySize: 512,
|
|
||||||
Runtime: "python3.7",
|
|
||||||
Code: &lambda.Function_Code{
|
|
||||||
ZipFile: "sshrimp-ca.zip",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the YAML AWS CloudFormation template
|
|
||||||
y, err := template.YAML()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return y, nil
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package ca
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Provider describes an AWS provider
|
|
||||||
type Provider struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Alias string `json:"alias"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
AllowedAccountIDs []string `json:"allowed_account_ids"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module describes a terraform module
|
|
||||||
type Module struct {
|
|
||||||
Source string `json:"source"`
|
|
||||||
Providers map[string]string `json:"providers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TerraformOutput represents the main.tf.json struct
|
|
||||||
type TerraformOutput struct {
|
|
||||||
Provider map[string][]Provider `json:"provider"`
|
|
||||||
Module map[string]Module `json:"module"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTerraform(c *config.SSHrimp) ([]byte, error) {
|
|
||||||
|
|
||||||
providers := make([]Provider, len(c.CertificateAuthority.Regions))
|
|
||||||
modules := make(map[string]Module, len(c.CertificateAuthority.Regions))
|
|
||||||
for index, region := range c.CertificateAuthority.Regions {
|
|
||||||
providers[index].Version = "~> 2.49"
|
|
||||||
providers[index].Alias = region
|
|
||||||
providers[index].Region = region
|
|
||||||
providers[index].AllowedAccountIDs = []string{
|
|
||||||
strconv.Itoa(c.CertificateAuthority.AccountID),
|
|
||||||
}
|
|
||||||
modules["sshrimp-"+region] = Module{
|
|
||||||
Source: "./terraform",
|
|
||||||
Providers: map[string]string{
|
|
||||||
"aws": "aws." + region,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output := TerraformOutput{
|
|
||||||
Provider: map[string][]Provider{
|
|
||||||
"aws": providers,
|
|
||||||
},
|
|
||||||
Module: modules,
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.MarshalIndent(output, "", " ")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user