Skip to content

Commit

Permalink
Merge pull request #8 from csatib02/generic-functionalities
Browse files Browse the repository at this point in the history
Setup generic functionality
  • Loading branch information
csatib02 authored Nov 3, 2023
2 parents 7f6496c + bd1f581 commit e542edf
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 2 deletions.
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
module github.com/bank-vaults/secret-init

go 1.21

require (
github.com/samber/slog-multi v1.0.2
github.com/samber/slog-syslog v1.0.0
github.com/spf13/cast v1.5.1
)

require (
github.com/samber/lo v1.38.1 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ=
github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo=
github.com/samber/slog-syslog v1.0.0 h1:4tf8sNv9+qTQ6Fj8+N6U1ZEtUbqbAIzd+q26/NegWFM=
github.com/samber/slog-syslog v1.0.0/go.mod h1:jjupk+yHPVSuXuGhKleoClYc/HEaC+Ro5X4YYeBrt6g=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
177 changes: 175 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Copyright © 2018 Banzai Cloud
// Copyright © 2023 Bank-Vaults Maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -15,6 +14,180 @@

package main

import (
"context"
"errors"
"fmt"
"log/slog"
"net"
"os"
"os/exec"
"os/signal"
"slices"
"syscall"
"time"

slogmulti "github.com/samber/slog-multi"
slogsyslog "github.com/samber/slog-syslog"
"github.com/spf13/cast"

"github.com/bank-vaults/secret-init/provider"
)

func main() {
println("hello world")
var logger *slog.Logger
{
var level slog.Level

err := level.UnmarshalText([]byte(os.Getenv("VAULT_LOG_LEVEL")))
if err != nil { // Silently fall back to info level
level = slog.LevelInfo
}

levelFilter := func(levels ...slog.Level) func(ctx context.Context, r slog.Record) bool {
return func(ctx context.Context, r slog.Record) bool {
return slices.Contains(levels, r.Level)
}
}

router := slogmulti.Router()

if cast.ToBool(os.Getenv("VAULT_JSON_LOG")) {
// Send logs with level higher than warning to stderr
router = router.Add(
slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}),
levelFilter(slog.LevelWarn, slog.LevelError),
)

// Send info and debug logs to stdout
router = router.Add(
slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}),
levelFilter(slog.LevelDebug, slog.LevelInfo),
)
} else {
// Send logs with level higher than warning to stderr
router = router.Add(
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}),
levelFilter(slog.LevelWarn, slog.LevelError),
)

// Send info and debug logs to stdout
router = router.Add(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}),
levelFilter(slog.LevelDebug, slog.LevelInfo),
)
}

if logServerAddr := os.Getenv("VAULT_ENV_LOG_SERVER"); logServerAddr != "" {
writer, err := net.Dial("udp", logServerAddr)

// We silently ignore syslog connection errors for the lack of a better solution
if err == nil {
router = router.Add(slogsyslog.Option{Level: slog.LevelInfo, Writer: writer}.NewSyslogHandler())
}
}

// TODO: add level filter handler
logger = slog.New(router.Handler())
logger = logger.With(slog.String("app", "vault-secret-init"))

slog.SetDefault(logger)
}

// TODO: enable providers
var provider provider.Provider

if len(os.Args) == 1 {
logger.Error("no command is given, vault-env can't determine the entrypoint (command), please specify it explicitly or let the webhook query it (see documentation)")

os.Exit(1)
}

daemonMode := cast.ToBool(os.Getenv("VAULT_ENV_DAEMON"))
delayExec := cast.ToDuration(os.Getenv("VAULT_ENV_DELAY"))

entrypointCmd := os.Args[1:]

binary, err := exec.LookPath(entrypointCmd[0])
if err != nil {
logger.Error("binary not found", slog.String("binary", entrypointCmd[0]))

os.Exit(1)
}

ctx := context.Background()
envs, err := provider.LoadSecrets(ctx, os.Environ())
if err != nil {
logger.Error("could not retrieve secrets from the provider.", err)

os.Exit(1)
}

sigs := make(chan os.Signal, 1)

if delayExec > 0 {
logger.Info(fmt.Sprintf("sleeping for %s...", delayExec))
time.Sleep(delayExec)
}

logger.Info("spawning process", slog.String("entrypoint", fmt.Sprint(entrypointCmd)))

if daemonMode {
logger.Info("in daemon mode...")
cmd := exec.Command(binary, entrypointCmd[1:]...)
cmd.Env = append(os.Environ(), envs...)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout

signal.Notify(sigs)

err = cmd.Start()
if err != nil {
logger.Error(fmt.Errorf("failed to start process: %w", err).Error(), slog.String("entrypoint", fmt.Sprint(entrypointCmd)))

os.Exit(1)
}

go func() {
for sig := range sigs {
// We don't want to signal a non-running process.
if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
break
}

err := cmd.Process.Signal(sig)
if err != nil {
logger.Warn(fmt.Errorf("failed to signal process: %w", err).Error(), slog.String("signal", sig.String()))
} else {
logger.Info("received signal", slog.String("signal", sig.String()))
}
}
}()

err = cmd.Wait()

close(sigs)

if err != nil {
exitCode := -1
// try to get the original exit code if possible
var exitError *exec.ExitError
if errors.As(err, &exitError) {
exitCode = exitError.ExitCode()
}

logger.Error(fmt.Errorf("failed to exec process: %w", err).Error(), slog.String("entrypoint", fmt.Sprint(entrypointCmd)))

os.Exit(exitCode)
}

os.Exit(cmd.ProcessState.ExitCode())
}
err = syscall.Exec(binary, entrypointCmd, envs)
if err != nil {
logger.Error(fmt.Errorf("failed to exec process: %w", err).Error(), slog.String("entrypoint", fmt.Sprint(entrypointCmd)))

os.Exit(1)
}
}
22 changes: 22 additions & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright © 2023 Bank-Vaults Maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package provider

import "context"

// Provider is an interface for securely loading secrets based on environment variables.
type Provider interface {
LoadSecrets(ctx context.Context, paths []string) ([]string, error)
}

0 comments on commit e542edf

Please sign in to comment.