Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58016c6889 | ||
|
|
8989dc25ac | ||
|
|
899aad07b2 | ||
|
|
2bc73596d3 | ||
|
|
00de399557 | ||
|
|
4de02548c1 | ||
|
|
fbca16fc9a | ||
|
|
d504f22925 | ||
|
|
3e77c30ed3 | ||
|
|
9573438f75 |
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.
|
||||||
|
|||||||
@@ -7,20 +7,19 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func logRotate(path string, count int) error {
|
func logRotate(path string, count int) {
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
ext := filepath.Ext(path)
|
ext := filepath.Ext(path)
|
||||||
base := strings.TrimSuffix(path, ext)
|
base := strings.TrimSuffix(path, ext)
|
||||||
for i := count - 1; i >= 1; i-- {
|
for i := count - 1; i >= 1; i-- {
|
||||||
source := fmt.Sprintf("%s.%d%s", base, i, ext)
|
source := fmt.Sprintf("%s.%d%s", base, i, ext)
|
||||||
dest := fmt.Sprintf("%s.%d%s", base, i+1, ext)
|
dest := fmt.Sprintf("%s.%d%s", base, i+1, ext)
|
||||||
os.Remove(dest)
|
_ = os.Remove(dest)
|
||||||
os.Rename(source, dest)
|
_ = os.Rename(source, dest)
|
||||||
}
|
}
|
||||||
dest := fmt.Sprintf("%s.%d%s", base, 1, ext)
|
dest := fmt.Sprintf("%s.%d%s", base, 1, ext)
|
||||||
os.Remove(dest)
|
_ = os.Remove(dest)
|
||||||
os.Rename(path, dest)
|
_ = os.Rename(path, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -17,18 +16,20 @@ import (
|
|||||||
"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"
|
"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 (
|
var (
|
||||||
sigExit = []os.Signal{os.Kill, os.Interrupt}
|
sigExit = []os.Signal{os.Kill, os.Interrupt}
|
||||||
sigIgnore []os.Signal
|
sigIgnore []os.Signal
|
||||||
logger = logrus.New()
|
logger = logrus.New()
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
appname = "sshrimp"
|
appname = "sshrimp"
|
||||||
@@ -38,6 +39,7 @@ type cfg struct {
|
|||||||
Config string
|
Config string
|
||||||
LogDirectory string
|
LogDirectory string
|
||||||
Verbose bool
|
Verbose bool
|
||||||
|
Foreground bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLogDir() string {
|
func getLogDir() string {
|
||||||
@@ -72,8 +74,8 @@ func setupLoging(config cfg) error {
|
|||||||
logrus.ErrorLevel,
|
logrus.ErrorLevel,
|
||||||
logrus.WarnLevel,
|
logrus.WarnLevel,
|
||||||
}
|
}
|
||||||
err := os.MkdirAll(config.LogDirectory, 0750)
|
err := os.MkdirAll(config.LogDirectory, 0o750)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +93,7 @@ func setupLoging(config cfg) error {
|
|||||||
Writer: os.Stderr,
|
Writer: os.Stderr,
|
||||||
LogLevels: levels,
|
LogLevels: levels,
|
||||||
})
|
})
|
||||||
logger.Out = ioutil.Discard
|
logger.Out = io.Discard
|
||||||
file, err := os.Create(logName)
|
file, err := os.Create(logName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -104,7 +106,6 @@ func setupLoging(config cfg) error {
|
|||||||
log = logger.WithFields(logrus.Fields{
|
log = logger.WithFields(logrus.Fields{
|
||||||
"pid": os.Getpid(),
|
"pid": os.Getpid(),
|
||||||
})
|
})
|
||||||
log.Logger.Info("testing")
|
|
||||||
|
|
||||||
sshrimpagent.Log = log
|
sshrimpagent.Log = log
|
||||||
signer.Log = log
|
signer.Log = log
|
||||||
@@ -123,74 +124,221 @@ func ExpandPath(path string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main2(cli cfg, c *config.SSHrimp) {
|
||||||
var cli cfg
|
err := setupLoging(cli)
|
||||||
flag.StringVar(&cli.Config, "config", config.GetPath(), "sshrimp config file")
|
|
||||||
flag.StringVar(&cli.LogDirectory, "log", getLogDir(), "sshrimp log directory")
|
|
||||||
flag.BoolVar(&cli.Verbose, "v", false, "enable verbose logging")
|
|
||||||
fmt.Println(getLogDir())
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if err := setupLoging(cli); err != nil {
|
|
||||||
logger.Warnf("Error setting up logging: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := config.NewSSHrimpWithDefaults()
|
|
||||||
err := c.Read(cli.Config)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Warnf("Error setting up logging: %v", err)
|
||||||
}
|
}
|
||||||
err = launchAgent(c)
|
listener := openSocket(ExpandPath(c.Agent.Socket))
|
||||||
|
if listener == nil {
|
||||||
|
log.Errorln("Failed to open socket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = launchAgent(c, listener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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()
|
||||||
|
if cli.Verbose {
|
||||||
|
logger.SetLevel(logrus.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Debug("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()
|
||||||
|
logger.Debugf("Agent started in the background check %s for logs", getLogDir())
|
||||||
|
}
|
||||||
|
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
|
||||||
sshSigner ssh.Signer
|
|
||||||
logMessage string
|
logMessage string
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Traceln("Creating socket")
|
if socketWorks(socketPath) { // socket is accepting connections
|
||||||
if _, err = os.Stat(ExpandPath(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", ExpandPath(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", ExpandPath(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
|
||||||
}
|
}
|
||||||
@@ -204,18 +352,19 @@ func launchAgent(c *config.SSHrimp) error {
|
|||||||
log.Traceln("Creating new sshrimp agent from sshSigner and config")
|
log.Traceln("Creating new sshrimp agent from sshSigner and config")
|
||||||
sshrimpAgent, err := sshrimpagent.NewSSHrimpAgent(c, sshSigner)
|
sshrimpAgent, err := sshrimpagent.NewSSHrimpAgent(c, sshSigner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logger.Errorf("Failed to create sshrimpAgent: %v", err)
|
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("Ignoring signals: %v", sigIgnore)
|
log.Debugf("Ignoring signals: %v", sigIgnore)
|
||||||
signal.Ignore(sigIgnore...)
|
signal.Ignore(sigIgnore...)
|
||||||
log.Debugf("Exiting on signals: %v", sigExit)
|
log.Debugf("Exiting on signals: %v", sigExit)
|
||||||
osSignals := make(chan os.Signal)
|
osSignals := make(chan os.Signal, 10)
|
||||||
signal.Notify(osSignals, sigExit...)
|
signal.Notify(osSignals, sigExit...)
|
||||||
go func() {
|
go func() {
|
||||||
<-osSignals
|
sig := <-osSignals
|
||||||
listener.Close()
|
log.Infof("Recieved signal %v: closing", sig)
|
||||||
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Traceln("Starting main loop")
|
log.Traceln("Starting main loop")
|
||||||
@@ -227,14 +376,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
|
||||||
|
|
||||||
|
|||||||
@@ -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.KeyAlgoRSASHA256)
|
|
||||||
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/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.KeyAlgoRSASHA256)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
73
go.mod
73
go.mod
@@ -1,55 +1,34 @@
|
|||||||
module git.narnian.us/lordwelch/sshrimp
|
module gitea.narnian.us/lordwelch/sshrimp
|
||||||
|
|
||||||
go 1.18
|
go 1.24.0
|
||||||
|
|
||||||
|
toolchain go1.24.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/kms v1.8.0
|
github.com/BurntSushi/toml v1.6.0
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
github.com/coreos/go-oidc/v3 v3.17.0
|
||||||
github.com/BurntSushi/toml v1.2.1
|
github.com/google/uuid v1.6.0
|
||||||
github.com/aws/aws-lambda-go v1.37.0
|
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||||
github.com/aws/aws-sdk-go v1.44.180
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
github.com/awslabs/goformation/v4 v4.19.5
|
github.com/prometheus/procfs v0.19.2
|
||||||
github.com/coreos/go-oidc/v3 v3.5.0
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/google/uuid v1.3.0
|
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.14.0
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||||
github.com/sirupsen/logrus v1.9.0
|
|
||||||
github.com/zitadel/oidc v1.12.1
|
|
||||||
golang.org/x/crypto v0.5.0
|
|
||||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4
|
|
||||||
google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.108.0 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||||
cloud.google.com/go/compute v1.15.1 // indirect
|
github.com/gorilla/schema v1.4.1 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
cloud.google.com/go/iam v0.10.0 // indirect
|
github.com/jeremija/gosubmit v0.2.8 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/zitadel/logging v0.6.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
|
golang.org/x/oauth2 v0.34.0 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
github.com/gorilla/schema v1.2.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
|
||||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect
|
|
||||||
github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 // indirect
|
|
||||||
go.opencensus.io v0.24.0 // indirect
|
|
||||||
golang.org/x/net v0.5.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.4.0 // indirect
|
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
|
||||||
golang.org/x/term v0.4.0 // indirect
|
|
||||||
golang.org/x/text v0.6.0 // indirect
|
|
||||||
google.golang.org/api v0.107.0 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
|
||||||
google.golang.org/grpc v1.52.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
622
go.sum
622
go.sum
@@ -1,592 +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/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||||
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.108.0 h1:xntQwnfn8oHGX0crLVinvHM+AhXvi3QHQIEcX/2hiWk=
|
|
||||||
cloud.google.com/go v0.108.0/go.mod h1:lNUfQqusBJp0bgAg6qrHgYFYbTB+dOiob1itwnlD33Q=
|
|
||||||
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/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE=
|
|
||||||
cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
|
||||||
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/iam v0.10.0 h1:fpP/gByFs6US1ma53v7VxhvbJpO2Aapng6wabJ99MuI=
|
|
||||||
cloud.google.com/go/iam v0.10.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM=
|
|
||||||
cloud.google.com/go/kms v1.8.0 h1:VrJLOsMRzW7IqTTYn+OYupqF3iKSE060Nrn+PECrYjg=
|
|
||||||
cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=
|
|
||||||
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
|
|
||||||
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=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
|
||||||
github.com/aws/aws-lambda-go v1.37.0 h1:WXkQ/xhIcXZZ2P5ZBEw+bbAKeCEcb5NtiYpSwVVzIXg=
|
|
||||||
github.com/aws/aws-lambda-go v1.37.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
|
||||||
github.com/awslabs/goformation/v4 v4.19.5 h1:Y+Tzh01tWg8gf//AgGKUamaja7Wx9NPiJf1FpZu4/iU=
|
|
||||||
github.com/awslabs/goformation/v4 v4.19.5/go.mod h1:JoNpnVCBOUtEz9bFxc9sjy8uBUCLF5c4D1L7RhRTVM8=
|
|
||||||
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/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
|
|
||||||
github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
|
|
||||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
|
||||||
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
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-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
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/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
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/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/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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM=
|
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
|
||||||
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/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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
|
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
|
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||||
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
|
||||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
|
||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
|
||||||
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
|
|
||||||
github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
|
|
||||||
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/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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
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/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.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
|
||||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.16.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134=
|
|
||||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.12.0 h1:p4oGGk2M2UJc0wWN4lHFvIB71lxsh0T/UiKCCgFADY8=
|
|
||||||
github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
|
||||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||||
github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
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/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
|
||||||
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/zitadel/oidc v1.13.5 h1:7jhh68NGZitLqwLiVU9Dtwa4IraJPFF1vS+4UupO93U=
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
github.com/zitadel/oidc v1.13.5/go.mod h1:rHs1DhU3Sv3tnI6bQRVlFa3u0lCwtR7S21WHY+yXgPA=
|
||||||
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=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM=
|
|
||||||
github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0=
|
|
||||||
github.com/zitadel/oidc v1.12.1 h1:f+NB+GnNhygzFulgRhsvxJAJnmfcgPTfFSIJmgBIGWw=
|
|
||||||
github.com/zitadel/oidc v1.12.1/go.mod h1:RSZbbTbwvbP6cXdw9sj/mjXWHSK+p9s2jqArOlk+81Q=
|
|
||||||
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/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
|
||||||
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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
|
||||||
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/exp v0.0.0-20230113213754-f9f960f08ad4 h1:CNkDRtCj8otM5CFz5jYvbr8ioXX8flVsLfDWEj0M5kk=
|
|
||||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
|
||||||
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/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/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
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-20180906233101-161cd47e91fd/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/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-20200520004742-59133d7f0dd7/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/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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
|
||||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
|
||||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
|
||||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
|
||||||
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-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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
|
||||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
|
||||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
|
||||||
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/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
|
||||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
|
||||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
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/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/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
|
||||||
google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU=
|
|
||||||
google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
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/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-20230113154510-dbe35b8444a5 h1:wJT65XLOzhpSPCdAmmKfz94SlmnQzDzjm3Cj9k3fsXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
|
||||||
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/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
|
|
||||||
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
|
|
||||||
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/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/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=
|
|
||||||
|
|||||||
@@ -5,37 +5,29 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"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
|
||||||
Scopes []string
|
KeyPath string
|
||||||
KeyPath string
|
Port int
|
||||||
Port int
|
CAUrls []string
|
||||||
Url 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
|
||||||
UsernameRegexs []string
|
UsernameRegexs []string
|
||||||
@@ -51,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",
|
||||||
@@ -104,7 +63,6 @@ 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",
|
||||||
@@ -112,8 +70,6 @@ func NewSSHrimpWithDefaults() *SSHrimp {
|
|||||||
Scopes: []string{"openid", "email", "profile"},
|
Scopes: []string{"openid", "email", "profile"},
|
||||||
},
|
},
|
||||||
CertificateAuthority{
|
CertificateAuthority{
|
||||||
FunctionName: "sshrimp",
|
|
||||||
KeyAlias: "alias/sshrimp",
|
|
||||||
ForceCommandRegex: "^$",
|
ForceCommandRegex: "^$",
|
||||||
SourceAddressRegex: "^$",
|
SourceAddressRegex: "^$",
|
||||||
UsernameRegexs: []string{`^(.*)@example\.com$`},
|
UsernameRegexs: []string{`^(.*)@example\.com$`},
|
||||||
@@ -132,22 +88,7 @@ func NewSSHrimpWithDefaults() *SSHrimp {
|
|||||||
return &sshrimp
|
return &sshrimp
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultPath of the sshrimp config file
|
func validateInt(val any) error {
|
||||||
var DefaultPath = ".ssh/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
|
|
||||||
}
|
|
||||||
home, _ := os.UserHomeDir()
|
|
||||||
return filepath.Join(home, 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
|
||||||
@@ -159,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
|
||||||
@@ -171,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
|
||||||
@@ -183,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/")
|
||||||
@@ -195,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.UsernameClaims[0],
|
|
||||||
},
|
|
||||||
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.UsernameRegexs[0],
|
|
||||||
},
|
|
||||||
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
|
||||||
@@ -410,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,
|
||||||
|
}
|
||||||
@@ -3,57 +3,27 @@ package identity
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Disable log prefixes such as the default timestamp.
|
|
||||||
// Prefix text prevents the message from being parsed as JSON.
|
|
||||||
// A timestamp is added when shipping logs to Cloud Logging.
|
|
||||||
log.SetFlags(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry defines a log entry.
|
|
||||||
type Entry struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Severity string `json:"severity,omitempty"`
|
|
||||||
Trace string `json:"logging.googleapis.com/trace,omitempty"`
|
|
||||||
|
|
||||||
// Logs Explorer allows filtering and display of this as `jsonPayload.component`.
|
|
||||||
Component string `json:"component,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// String renders an entry structure to the JSON format expected by Cloud Logging.
|
|
||||||
func (e Entry) String() string {
|
|
||||||
if e.Severity == "" {
|
|
||||||
e.Severity = "INFO"
|
|
||||||
}
|
|
||||||
out, err := json.Marshal(e)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("json.Marshal: %v", err)
|
|
||||||
}
|
|
||||||
return string(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
usernameREs []*regexp.Regexp
|
usernameREs []*regexp.Regexp
|
||||||
usernameClaims []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 {
|
||||||
@@ -75,6 +45,7 @@ func NewIdentity(c *config.SSHrimp) (*Identity, error) {
|
|||||||
verifier: provider.Verifier(oidcConfig),
|
verifier: provider.Verifier(oidcConfig),
|
||||||
usernameREs: regexes,
|
usernameREs: regexes,
|
||||||
usernameClaims: c.CertificateAuthority.UsernameClaims,
|
usernameClaims: c.CertificateAuthority.UsernameClaims,
|
||||||
|
log: log,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,14 +60,18 @@ func (i *Identity) Validate(token string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Identity) getUsernames(idToken *oidc.IDToken) ([]string, error) {
|
func (i *Identity) getUsernames(idToken *oidc.IDToken) ([]string, error) {
|
||||||
var claims map[string]interface{}
|
var claims map[string]any
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
return nil, 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))
|
usernames := make([]string, 0, len(i.usernameClaims))
|
||||||
for idx, claim := range i.usernameClaims {
|
for idx, claim := range i.usernameClaims {
|
||||||
|
|
||||||
claimedUsernames := getClaim(claim, claims)
|
claimedUsernames := i.getClaim(claim, claims)
|
||||||
|
|
||||||
|
if len(claimedUsernames) == 0 {
|
||||||
|
i.log.Errorf("Did not find a username using: getClaim(%#v, %#v)", claim, claims)
|
||||||
|
}
|
||||||
|
|
||||||
if idx < len(i.usernameREs) {
|
if idx < len(i.usernameREs) {
|
||||||
for _, name := range claimedUsernames {
|
for _, name := range claimedUsernames {
|
||||||
@@ -104,8 +79,10 @@ func (i *Identity) getUsernames(idToken *oidc.IDToken) ([]string, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
usernames = append(usernames, claimedUsernames...)
|
usernames = append(usernames, claimedUsernames...)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i.log.Infof("Adding usernames: %v", usernames)
|
||||||
if len(usernames) < 1 {
|
if len(usernames) < 1 {
|
||||||
return nil, errors.New("configured username claim not in identity token")
|
return nil, errors.New("configured username claim not in identity token")
|
||||||
}
|
}
|
||||||
@@ -119,27 +96,13 @@ func parseUsername(username string, re *regexp.Regexp) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClaim(claim string, claims map[string]interface{}) []string {
|
func (i *Identity) getClaim(claim string, claims map[string]any) []string {
|
||||||
usernames := make([]string, 0, 2)
|
usernames := make([]string, 0, 2)
|
||||||
parts := strings.Split(claim, ".")
|
parts := strings.Split(claim, ".")
|
||||||
f:
|
f:
|
||||||
for idx, part := range parts {
|
for idx, part := range parts {
|
||||||
if idx == len(parts)-1 {
|
|
||||||
name, ok := claims[part].(string)
|
|
||||||
if ok {
|
|
||||||
usernames = append(usernames, name)
|
|
||||||
}
|
|
||||||
return base64Decode(usernames)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(part)
|
|
||||||
log.Println(Entry{
|
|
||||||
Severity: "NOTICE",
|
|
||||||
Message: fmt.Sprintf("Fuck Off: %v", claims),
|
|
||||||
Component: part,
|
|
||||||
})
|
|
||||||
switch v := claims[part].(type) {
|
switch v := claims[part].(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
claims = v
|
claims = v
|
||||||
case []map[string]string:
|
case []map[string]string:
|
||||||
for _, claimItem := range v {
|
for _, claimItem := range v {
|
||||||
@@ -149,26 +112,29 @@ f:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break f
|
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:
|
default:
|
||||||
break f
|
break f
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return base64Decode(usernames)
|
return i.base64Decode(usernames)
|
||||||
}
|
}
|
||||||
func base64Decode(names []string) []string {
|
func (i *Identity) base64Decode(names []string) []string {
|
||||||
for idx, name := range names {
|
for idx, name := range names {
|
||||||
log.Println(Entry{
|
i.log.Debugf("Attempting to decode %q as base64\n", name)
|
||||||
Severity: "NOTICE",
|
|
||||||
Message: fmt.Sprintf("Attempting to decode %q as base64\n", name),
|
|
||||||
})
|
|
||||||
decoded, err := base64.RawURLEncoding.DecodeString(name)
|
decoded, err := base64.RawURLEncoding.DecodeString(name)
|
||||||
if err == nil && utf8.Valid(decoded) {
|
if err == nil && utf8.Valid(decoded) {
|
||||||
names[idx] = string(decoded)
|
names[idx] = string(decoded)
|
||||||
log.Println(Entry{
|
i.log.Debugf("Successfully decoded %q as base64\n", names[idx])
|
||||||
Severity: "NOTICE",
|
|
||||||
Message: fmt.Sprintf("Successfully decoded %q as base64\n", names[idx]),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return names
|
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,23 +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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"errors"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/config"
|
||||||
|
"gitea.narnian.us/lordwelch/sshrimp/internal/http"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"gitea.narnian.us/lordwelch/sshrimp/internal/identity"
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/identity"
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/lambda"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@@ -44,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
|
||||||
}
|
}
|
||||||
@@ -66,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)),
|
||||||
@@ -78,24 +69,20 @@ func SignCertificateGCP(publicKey ssh.PublicKey, token string, forceCommand stri
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var uri string
|
Log.Logger.Tracef("Posting to url %s", url)
|
||||||
if c.Agent.Url != "" {
|
result, err := http.Client.Post(url, "application/json", bytes.NewReader(payload))
|
||||||
uri = c.Agent.Url
|
|
||||||
} else {
|
|
||||||
uri = fmt.Sprintf("https://%s-%s.cloudfunctions.net/%s", region, c.CertificateAuthority.Project, c.CertificateAuthority.FunctionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := http.Post(uri, "application/json", bytes.NewReader(payload))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("http post failed: %w", err)
|
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, fmt.Errorf("failed to retrieve the response from sshrimp-ca: %w", err)
|
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, fmt.Errorf("failed to parse json response from sshrimp-ca: %w: %v", err, string(resbody))
|
return nil, fmt.Errorf("failed to parse json response from sshrimp-ca: %w: %v", err, string(resbody))
|
||||||
@@ -112,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
|
|
||||||
sess := session.Must(session.NewSession(&aws.Config{
|
|
||||||
Region: aws.String(region),
|
|
||||||
}))
|
|
||||||
lambdaService := lambda.New(sess)
|
|
||||||
|
|
||||||
// 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, fmt.Errorf("failed to parse json response from sshrimp-ca: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
||||||
@@ -176,7 +114,10 @@ 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)
|
||||||
|
if err != nil {
|
||||||
|
return ssh.Certificate{}, err
|
||||||
|
}
|
||||||
usernames, err := i.Validate(event.Token)
|
usernames, err := i.Validate(event.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ssh.Certificate{}, err
|
return ssh.Certificate{}, err
|
||||||
@@ -240,7 +181,7 @@ func ValidateRequest(event SSHrimpEvent, c *config.SSHrimp, requestID string, fu
|
|||||||
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"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package sshrimpagent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/crypto/ssh"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
"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/google/uuid"
|
||||||
"github.com/zitadel/oidc/pkg/client/rp"
|
"github.com/zitadel/oidc/pkg/client/rp"
|
||||||
httphelper "github.com/zitadel/oidc/pkg/http"
|
httphelper "github.com/zitadel/oidc/pkg/http"
|
||||||
@@ -16,9 +19,7 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var hashKey = []byte(uuid.New().String())[:16]
|
||||||
key = []byte(uuid.New().String())[:16]
|
|
||||||
)
|
|
||||||
|
|
||||||
type OidcClient struct {
|
type OidcClient struct {
|
||||||
ListenAddress string
|
ListenAddress string
|
||||||
@@ -37,7 +38,7 @@ func newOIDCClient(c *config.SSHrimp) (*OidcClient, error) {
|
|||||||
c.Agent.Scopes = append([]string{"scopes"}, c.Agent.Scopes...)
|
c.Agent.Scopes = append([]string{"scopes"}, c.Agent.Scopes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
token_chan := make(chan *oidc.Tokens)
|
token := make(chan *oidc.Tokens)
|
||||||
|
|
||||||
oidcMux := http.NewServeMux()
|
oidcMux := http.NewServeMux()
|
||||||
return &OidcClient{
|
return &OidcClient{
|
||||||
@@ -50,7 +51,7 @@ func newOIDCClient(c *config.SSHrimp) (*OidcClient, error) {
|
|||||||
WriteTimeout: time.Minute / 2,
|
WriteTimeout: time.Minute / 2,
|
||||||
IdleTimeout: time.Minute / 2,
|
IdleTimeout: time.Minute / 2,
|
||||||
},
|
},
|
||||||
OIDCToken: token_chan,
|
OIDCToken: token,
|
||||||
Certificate: &ssh.Certificate{},
|
Certificate: &ssh.Certificate{},
|
||||||
SSHrimp: c,
|
SSHrimp: c,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -72,7 +73,7 @@ func (o *OidcClient) ListenAndServe() error {
|
|||||||
if err = o.setupHandlers(); err != nil {
|
if err = o.setupHandlers(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return o.Server.Serve(ln)
|
return o.Serve(ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OidcClient) setupHandlers() error {
|
func (o *OidcClient) setupHandlers() error {
|
||||||
@@ -80,18 +81,23 @@ func (o *OidcClient) setupHandlers() error {
|
|||||||
redirectURI.Path = "/auth/callback"
|
redirectURI.Path = "/auth/callback"
|
||||||
successURI := o.baseURI()
|
successURI := o.baseURI()
|
||||||
successURI.Path = "/success"
|
successURI.Path = "/success"
|
||||||
// failURI := o.baseURI()
|
var CAKey []byte
|
||||||
// failURI.RawQuery = url.Values{"auth":[]string{"fail"}}.Encode()
|
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(key, key, httphelper.WithUnsecure())
|
cookieHandler := httphelper.NewCookieHandler(hashKey, nil)
|
||||||
|
|
||||||
options := []rp.Option{
|
options := []rp.Option{
|
||||||
rp.WithCookieHandler(cookieHandler),
|
rp.WithCookieHandler(cookieHandler),
|
||||||
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
|
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
|
||||||
|
rp.WithHTTPClient(sshrimp_http.Client),
|
||||||
}
|
}
|
||||||
if o.Agent.ClientSecret == "" {
|
options = append(options, rp.WithPKCE(cookieHandler))
|
||||||
options = append(options, rp.WithPKCE(cookieHandler))
|
|
||||||
}
|
|
||||||
if o.Agent.KeyPath != "" {
|
if o.Agent.KeyPath != "" {
|
||||||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyPath(o.Agent.KeyPath)))
|
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyPath(o.Agent.KeyPath)))
|
||||||
}
|
}
|
||||||
@@ -113,19 +119,34 @@ func (o *OidcClient) setupHandlers() error {
|
|||||||
o.oidcMux.Handle("/login", rp.AuthURLHandler(state, provider))
|
o.oidcMux.Handle("/login", rp.AuthURLHandler(state, provider))
|
||||||
o.oidcMux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
o.oidcMux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if o.Certificate != nil && o.Certificate.SignatureKey != nil {
|
if o.Certificate != nil && o.Certificate.SignatureKey != nil {
|
||||||
fmt.Fprintf(w, "The SSH CA currently in use is:\n%s", ssh.MarshalAuthorizedKey(o.Certificate.SignatureKey))
|
key := ssh.MarshalAuthorizedKey(o.Certificate.SignatureKey)
|
||||||
Log.Printf("The SSH CA currently in use is:\n%s", 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) {
|
o.oidcMux.Handle(successURI.Path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintln(w, "Return to the CLI.")
|
fmt.Fprintln(w, "Return to the CLI.")
|
||||||
if o.Certificate != nil && o.Certificate.SignatureKey != nil {
|
if o.Certificate != nil && o.Certificate.SignatureKey != nil {
|
||||||
fmt.Fprintf(w, "The SSH CA currently in use is: %s", ssh.MarshalAuthorizedKey(o.Certificate.SignatureKey))
|
key := ssh.MarshalAuthorizedKey(o.Certificate.SignatureKey)
|
||||||
Log.Printf("The SSH CA currently in use is:\n%s", 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)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// for demonstration purposes the returned userinfo response is written as JSON object onto response
|
|
||||||
marshalUserinfo := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens, state string, rp rp.RelyingParty) {
|
marshalUserinfo := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens, state string, rp rp.RelyingParty) {
|
||||||
o.OIDCToken <- tokens
|
o.OIDCToken <- tokens
|
||||||
w.Header().Add("location", successURI.String())
|
w.Header().Add("location", successURI.String())
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/pkg/browser"
|
"github.com/pkg/browser"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/zitadel/oidc/pkg/oidc"
|
"github.com/zitadel/oidc/pkg/oidc"
|
||||||
@@ -28,17 +27,16 @@ type sshrimpAgent struct {
|
|||||||
|
|
||||||
// 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, error) {
|
func NewSSHrimpAgent(c *config.SSHrimp, signer ssh.Signer) (agent.Agent, error) {
|
||||||
|
|
||||||
oidcClient, err := newOIDCClient(c)
|
oidcClient, err := newOIDCClient(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
for {
|
||||||
if err = oidcClient.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
if err = oidcClient.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
Log.Logger.Errorf("Server failed: %v", err)
|
Log.Logger.Errorf("Server failed: %v", err)
|
||||||
os.Exit(99)
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -60,8 +58,8 @@ func (r *sshrimpAgent) authenticate() error {
|
|||||||
err = errors.New("no token provided")
|
err = errors.New("no token provided")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log.Debugln("Token is expired re-authenticating")
|
Log.Debugln("Token is expired re-authenticating http://" + r.oidcClient.Addr + "/login")
|
||||||
browser.OpenURL("http://" + r.oidcClient.Addr + "/login")
|
_ = browser.OpenURL("http://" + r.oidcClient.Addr + "/login")
|
||||||
select {
|
select {
|
||||||
case r.token = <-r.oidcClient.OIDCToken:
|
case r.token = <-r.oidcClient.OIDCToken:
|
||||||
return nil
|
return nil
|
||||||
@@ -105,14 +103,13 @@ func (r *sshrimpAgent) List() ([]*agent.Key, error) {
|
|||||||
Log.Traceln("Certificate has expired")
|
Log.Traceln("Certificate has expired")
|
||||||
Log.Traceln("authenticating token")
|
Log.Traceln("authenticating token")
|
||||||
err := r.authenticate()
|
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
|
||||||
@@ -172,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
|
||||||
}
|
}
|
||||||
|
|||||||
39
magefile.go
39
magefile.go
@@ -1,39 +0,0 @@
|
|||||||
//go:build mage
|
|
||||||
// +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{
|
|
||||||
{Filename: "go.mod"},
|
|
||||||
{"gcp/gcp.go", "gcp.go"},
|
|
||||||
{Filename: "internal"},
|
|
||||||
{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