Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Inject contents of INI files into environment variables for templates and wrapped app #95

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9c4d877
Fixed up some of the golint warnings, added ability to inject content…
sychan Oct 26, 2017
76a9eac
Enable HTTP fetch of environment using custom headers
sychan Oct 26, 2017
e099856
Some cosmetic cleanup, apply validateCert to dependency polling as well
sychan Oct 26, 2017
7187c93
Tweak verbiage
sychan Oct 26, 2017
dd7aa3a
Set default transport to http.DefaultTransport, docs claim nil will r…
sychan Oct 27, 2017
f750864
Merge pull request #1 from jwilder/master
sychan May 1, 2018
7230dac
Add options to set uid and gid
sychan May 1, 2018
8f01f95
Use alternate implementation of Setuid/Setgid
sychan May 2, 2018
121378c
Add a logging message
sychan May 2, 2018
e76ceb2
Move setuid/setguid to goroutine context for program to be run
sychan May 2, 2018
28ad938
Checking for creation of logfile, without CPU spin, but bail if too m…
sychan May 7, 2018
aecc6fc
Lower the retry count to 30
sychan May 7, 2018
be7a7d7
Merge pull request #2 from sychan/master
sychan May 7, 2018
ee103db
Move binaries to the master branch
sychan May 7, 2018
264ece8
Get rid of code that bails out after too many failures on tail.
sychan May 7, 2018
39348e3
Get rid of stylistic warnings
sychan May 7, 2018
f15f0d2
Fix errors in how errors were being handled
sychan May 8, 2018
14d2e83
Fix conflation of line.Err and t.Err
sychan May 8, 2018
6f0e736
Zero the error counter after successful tail line
sychan May 8, 2018
cb8bada
Merge branch 'master' into master
sychan May 8, 2018
6d55177
Merge pull request #3 from sychan/master
sychan May 8, 2018
1c2a8d8
Build new binaries
sychan May 8, 2018
9f36c67
Add support for multiline INI entries in -env file
sychan Nov 7, 2018
68be968
Rebuild binaries
sychan Nov 7, 2018
010adb9
Rebuilt binaries after merge
sychan Nov 7, 2018
ba6f872
Get rid of warning about default initial value being 0
sychan Jan 7, 2019
ec5f449
Add ability to pull down file and write to destination.
sychan Jan 7, 2019
ed320f5
Rebuilt the binaries for --httpFile support
sychan Jan 7, 2019
90fa2a4
Adding `LICENSE`
jsfillman Mar 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright (c) 2020 The KBase Project and its Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

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.
Binary file added dockerize-alpine-linux-amd64-v0.6.1.tar.gz
Binary file not shown.
Binary file added dockerize-darwin-amd64-v0.6.1.tar.gz
Binary file not shown.
Binary file added dockerize-linux-386-v0.6.1.tar.gz
Binary file not shown.
Binary file added dockerize-linux-amd64-v0.6.1.tar.gz
Binary file not shown.
Binary file added dockerize-linux-armel-v0.6.1.tar.gz
Binary file not shown.
Binary file added dockerize-linux-armhf-v0.6.1.tar.gz
Binary file not shown.
16 changes: 16 additions & 0 deletions exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ import (
func runCmd(ctx context.Context, cancel context.CancelFunc, cmd string, args ...string) {
defer wg.Done()

if eGID >= 0 {
log.Printf("Setting effective gid to %d", eGID)
err := Setgid(eGID)
if err != nil {
log.Fatalf("Error while setting GID to %d: %s", eGID, err)
}
}

if eUID >= 0 {
log.Printf("Setting effective uid to %d", eUID)
err := Setuid(eUID)
if err != nil {
log.Fatalf("Error while setting UID to %d: %s", eUID, err)
}
}

process := exec.Command(cmd, args...)
process.Stdin = os.Stdin
process.Stdout = os.Stdout
Expand Down
147 changes: 142 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package main

import (
"crypto/tls"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
Expand All @@ -13,21 +16,26 @@ import (
"time"

"golang.org/x/net/context"
"gopkg.in/ini.v1"
)

const defaultWaitRetryInterval = time.Second

type sliceVar []string
type hostFlagsVar []string

// Context is the type passed into the template renderer
type Context struct {
}

type HttpHeader struct {
// HTTPHeader this is an optional header passed on http checks
type HTTPHeader struct {
name string
value string
}

// Env is bound to the template rendering Context and returns the
// environment variables passed to the program
func (c *Context) Env() map[string]string {
env := make(map[string]string)
for _, i := range os.Environ() {
Expand All @@ -43,20 +51,28 @@ var (
poll bool
wg sync.WaitGroup

envFlag string
multiline bool
envSection string
envHdrFlag sliceVar
validateCert bool
httpFileFlag sliceVar
templatesFlag sliceVar
templateDirsFlag sliceVar
stdoutTailFlag sliceVar
stderrTailFlag sliceVar
headersFlag sliceVar
delimsFlag string
delims []string
headers []HttpHeader
headers []HTTPHeader
urls []url.URL
waitFlag hostFlagsVar
waitRetryInterval time.Duration
waitTimeoutFlag time.Duration
dependencyChan chan struct{}
noOverwriteFlag bool
eUID int
eGID int

ctx context.Context
cancel context.CancelFunc
Expand Down Expand Up @@ -114,8 +130,15 @@ func waitForDependencies() {
case "http", "https":
wg.Add(1)
go func(u url.URL) {
var tr = http.DefaultTransport
if !validateCert {
tr = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
client := &http.Client{
Timeout: waitTimeoutFlag,
Timeout: waitTimeoutFlag,
Transport: tr,
}

defer wg.Done()
Expand Down Expand Up @@ -206,11 +229,83 @@ Arguments:
println(`For more information, see https://github.com/jwilder/dockerize`)
}

func getURL(url string, envHdrFlag []string) (iniFile []byte, err error) {

var resp *http.Response
var req *http.Request
var hdr string
var client *http.Client
var tr = http.DefaultTransport
// Define redirect handler to disallow redirects
var redir = func(req *http.Request, via []*http.Request) error {
return errors.New("Redirects disallowed")
}

if !validateCert {
tr = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
client = &http.Client{Transport: tr, CheckRedirect: redir}
req, err = http.NewRequest("GET", url, nil)
if err != nil {
// Weird problem with declaring client, bail
return
}

// Handle headers for request - are they headers or filepaths?
for _, h := range envHdrFlag {
if strings.Contains(h, ":") {
// This will break if path includes colon - don't use colons in path!
hdr = h
} else { // Treat this is a path to a secrets file containing header
var hdrFile []byte
hdrFile, err = ioutil.ReadFile(h)
if err != nil { // Could not read file, error out
return
}
hdr = string(hdrFile)
}
parts := strings.Split(hdr, ":")
if len(parts) != 2 {
log.Fatalf("Bad env-headers argument: %s. expected \"headerName: headerValue\"", hdr)
}
req.Header.Add(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]))
}
resp, err = client.Do(req)
if err == nil && resp.StatusCode == 200 {
defer resp.Body.Close()
iniFile, err = ioutil.ReadAll(resp.Body)
} else if err == nil { // Request completed with unexpected HTTP status code, bail
err = errors.New(resp.Status)
}
return
}

func getINI(envFlag string, envHdrFlag []string) (iniFile []byte, err error) {

// See if envFlag parses like an absolute URL, if so use http, otherwise treat as filename
url, urlERR := url.ParseRequestURI(envFlag)
if urlERR == nil && url.IsAbs() {
iniFile, err = getURL(envFlag, envHdrFlag)
} else {
iniFile, err = ioutil.ReadFile(envFlag)
}
return
}

func main() {

flag.BoolVar(&version, "version", false, "show version")
flag.BoolVar(&poll, "poll", false, "enable polling")

flag.StringVar(&envFlag, "env", "", "Optional path to INI file for injecting env vars. Does not overwrite existing env vars")
flag.BoolVar(&multiline, "multiline", false, "enable parsing multiline INI entries in INI environment file")
flag.StringVar(&envSection, "env-section", "", "Optional section of INI file to use for loading env vars. Defaults to \"\"")
flag.Var(&envHdrFlag, "env-header", "Optional string or path to secrets file for http headers passed if -env or -httpFile are URLs")
flag.BoolVar(&validateCert, "validate-cert", true, "Verify SSL certs for https connections")
flag.IntVar(&eGID, "egid", -1, "Set the numeric group ID for the running program") // Check for -1 later to skip
flag.IntVar(&eUID, "euid", -1, "Set the numeric user id for the running program")
flag.Var(&httpFileFlag, "httpFile", "Source URL and dest path (http://blah.com/blahblah~/dest). Pulls file from URL using env-header for auth and writes file to destination. Use tilde to separate URL from destination file")
flag.Var(&templatesFlag, "template", "Template (/template:/dest). Can be passed multiple times. Does also support directories")
flag.BoolVar(&noOverwriteFlag, "no-overwrite", false, "Do not overwrite destination file if it already exists.")
flag.Var(&stdoutTailFlag, "stdout", "Tails a file to stdout. Can be passed multiple times")
Expand All @@ -234,6 +329,25 @@ func main() {
os.Exit(1)
}

if envFlag != "" {
iniFile, err := getINI(envFlag, envHdrFlag)
if err != nil {
log.Fatalf("unreadable INI file %s: %s", envFlag, err)
}
cfg, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: multiline}, iniFile)
if err != nil {
log.Fatalf("error parsing contents of %s as INI format: %s", envFlag, err)
}
envHash := cfg.Section(envSection).KeysHash()

for k, v := range envHash {
if _, ok := os.LookupEnv(k); !ok {
// log.Printf("Setting %s to %s", k, v)
os.Setenv(k, v)
}
}
}

if delimsFlag != "" {
delims = strings.Split(delimsFlag, ":")
if len(delims) != 2 {
Expand Down Expand Up @@ -261,13 +375,34 @@ func main() {
if len(parts) != 2 {
log.Fatalf(errMsg, headersFlag)
}
headers = append(headers, HttpHeader{name: strings.TrimSpace(parts[0]), value: strings.TrimSpace(parts[1])})
headers = append(headers, HTTPHeader{name: strings.TrimSpace(parts[0]), value: strings.TrimSpace(parts[1])})
} else {
log.Fatalf(errMsg, headersFlag)
}

}

for _, httpFile := range httpFileFlag {
delim := "~"
if strings.Contains(httpFile, delim) {
parts := strings.Split(httpFile, delim)
if len(parts) != 2 {
log.Fatalf("bad httpFile argument: %s. expected \"URL;/dest\"", httpFile)
}
srcURL, dest := parts[0], parts[1]
httpSrc, err := getURL(srcURL, envHdrFlag)
if err != nil {
log.Fatalf("unable to fetch contents of url %s, error: %s", srcURL, err)
}
err = ioutil.WriteFile(dest, httpSrc, 0644)
if err != nil {
log.Fatalf("unable to write httpFile contents to %s, error: %s", dest, err)
}
} else {
log.Fatalf("-httpFile switch missing %s delimiter: %s", delim, httpFile)
}
}

for _, t := range templatesFlag {
template, dest := t, ""
if strings.Contains(t, ":") {
Expand Down Expand Up @@ -296,6 +431,8 @@ func main() {

if flag.NArg() > 0 {
wg.Add(1)
// Drop privs if passed the euid or egid params

go runCmd(ctx, cancel, flag.Arg(0), flag.Args()[1:]...)
}

Expand Down
26 changes: 26 additions & 0 deletions system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

// This has been cut/pasted from
// https://github.com/opencontainers/runc/blob/master/libcontainer/system/syscall_linux_64.go

import (
"golang.org/x/sys/unix"
)

// Setuid sets the uid of the calling thread to the specified uid.
func Setuid(uid int) (err error) {
_, _, e1 := unix.RawSyscall(unix.SYS_SETUID, uintptr(uid), 0, 0)
if e1 != 0 {
err = e1
}
return
}

// Setgid sets the gid of the calling thread to the specified gid.
func Setgid(gid int) (err error) {
_, _, e1 := unix.RawSyscall(unix.SYS_SETGID, uintptr(gid), 0, 0)
if e1 != 0 {
err = e1
}
return
}
27 changes: 20 additions & 7 deletions tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"os"
"time"

"github.com/hpcloud/tail"
"golang.org/x/net/context"
Expand All @@ -12,17 +13,25 @@ import (
func tailFile(ctx context.Context, file string, poll bool, dest *os.File) {
defer wg.Done()

var isPipe bool
var errCount int
const maxErr = 30
const sleepDur = 2 * time.Second

s, err := os.Stat(file)
if err != nil {
log.Fatalf("unable to stat %s: %s", file, err)
log.Printf("Warning: unable to stat %s: %s", file, err)
isPipe = false
} else {
isPipe = s.Mode()&os.ModeNamedPipe != 0
}

t, err := tail.TailFile(file, tail.Config{
Follow: true,
ReOpen: true,
Poll: poll,
Logger: tail.DiscardingLogger,
Pipe: s.Mode()&os.ModeNamedPipe != 0,
Pipe: isPipe,
})
if err != nil {
log.Fatalf("unable to tail %s: %s", file, err)
Expand All @@ -41,15 +50,19 @@ func tailFile(ctx context.Context, file string, poll bool, dest *os.File) {
return
// get the next log line and echo it out
case line := <-t.Lines:
if line == nil {
if t.Err() != nil {
log.Fatalf("unable to tail %s: %s", file, t.Err())
if line.Err != nil || (line == nil && t.Err() != nil) {
log.Printf("Warning: unable to tail %s: %s", file, t.Err())
errCount++
if errCount > maxErr {
log.Fatalf("Logged %d consecutive errors while tailing. Exiting", errCount)
}
time.Sleep(sleepDur)
continue
} else if line == nil {
return
} else if line.Err != nil {
log.Fatalf("unable to tail %s: %s", file, t.Err())
}
fmt.Fprintln(dest, line.Text)
errCount = 0 // Zero the error count
}
}
}
Loading