Compare commits

..

11 Commits

Author SHA1 Message Date
6f19b69bbd logging: add CREATE flag to file-based loggers. 2020-11-11 10:06:13 -08:00
7e118bfdb0 logging: add Multi 2020-11-11 09:52:07 -08:00
Kyle Isom
e0868841bf Merge pull request #7 from santosh653/master 2020-11-04 07:24:04 -08:00
santosh653
c558405d11 Update .travis.yml
Excluding go version 1.9 as only go version1.13 onwards are supported.
2020-11-04 05:22:10 -05:00
santosh653
a1eb035af7 Update .travis.yml
Adding Power support
2020-10-20 04:56:21 -04:00
5eedcff042 add rand package, utilities for math/rand. 2020-06-02 17:26:17 -07:00
Kyle Isom
6ac8eb04b4 Updated by OWASP Threat Dragon 2020-03-05 15:29:57 -08:00
Kyle Isom
4a4e4cd3fd Updated by OWASP Threat Dragon 2020-03-05 12:20:49 -08:00
Kyle Isom
1207093a56 Created by OWASP Threat Dragon 2020-03-05 12:19:57 -08:00
2b6ae03d1a config: add a Require message.
Also update explanation of the intended use-case for this package.
2020-03-02 17:36:29 -08:00
ef0f14a512 Add global config package.
This is a simple config system that I find myself wanting a lot.
2020-03-02 17:11:55 -08:00
8 changed files with 369 additions and 2 deletions

View File

@@ -1,8 +1,17 @@
arch:
- amd64
- ppc64le
sudo: false
language: go
go:
- tip
- 1.9
jobs:
exclude:
- go: 1.9
arch: amd64
- go: 1.9
arch: ppc64le
script:
- go get golang.org/x/lint/golint
- go get golang.org/x/tools/cmd/cover

110
config/config.go Normal file
View File

@@ -0,0 +1,110 @@
// Package config implements a simple global configuration system that
// supports a file with key=value pairs and environment variables. Note
// that the config system is global.
//
// This package is intended to be used for small daemons: some configuration
// file is optionally populated at program start, then this is used to
// transparently look up configuration values from either that file or the
// environment.
package config
import (
"bufio"
"log"
"os"
"strings"
)
// NB: Rather than define a singleton type, everything is defined at
// the top-level
var (
vars = map[string]string{}
prefix = ""
)
// SetEnvPrefix sets the prefix for all environment variables; it's
// assumed to not be needed for files.
func SetEnvPrefix(pfx string) {
prefix = pfx
}
func addLine(line string) {
if strings.HasPrefix(line, "#") || line == "" {
return
}
lineParts := strings.SplitN(line, "=", 2)
if len(lineParts) != 2 {
log.Print("skipping line: ", line)
return // silently ignore empty keys
}
lineParts[0] = strings.TrimSpace(lineParts[0])
lineParts[1] = strings.TrimSpace(lineParts[1])
vars[lineParts[0]] = lineParts[1]
}
// LoadFile scans the file at path for key=value pairs and adds them
// to the configuration.
func LoadFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
addLine(line)
}
if err = scanner.Err(); err != nil {
return err
}
return nil
}
// Get retrieves a value from either a configuration file or the
// environment. Note that values from a file will override environment
// variables.
func Get(key string) string {
if v, ok := vars[key]; ok {
return v
}
return os.Getenv(prefix + key)
}
// GetDefault retrieves a value from either a configuration file or
// the environment. Note that value from a file will override
// environment variables. If a value isn't found (e.g. Get returns an
// empty string), the default value will be used.
func GetDefault(key, def string) string {
if v := Get(key); v != "" {
return v
}
return def
}
// Require retrieves a value from either a configuration file or the
// environment. If the key isn't present, it will call log.Fatal, printing
// the missing key.
func Require(key string) string {
if v, ok := vars[key]; ok {
return v
}
v, ok := os.LookupEnv(prefix + key)
if !ok {
var envMessage string
if prefix != "" {
envMessage = " (note: looked for the key " + prefix + key
envMessage += " in the local env)"
}
log.Fatalf("missing required configuration value %s%s", key, envMessage)
}
return v
}

66
config/config_test.go Normal file
View File

@@ -0,0 +1,66 @@
package config
import (
"os"
"testing"
)
const (
testFilePath = "testdata/test.env"
// Keys
kOrder = "ORDER"
kSpecies = "SPECIES"
kName = "COMMON_NAME"
// Env
eOrder = "corvus"
eSpecies = "corvus corax"
eName = "northern raven"
// File
fOrder = "stringiformes"
fSpecies = "strix aluco"
// Name isn't set in the file to test fall through.
)
func init() {
os.Setenv(kOrder, eOrder)
os.Setenv(kSpecies, eSpecies)
os.Setenv(kName, eName)
}
func TestLoadEnvOnly(t *testing.T) {
order := Get(kOrder)
species := Get(kSpecies)
if order != eOrder {
t.Errorf("want %s, have %s", eOrder, order)
}
if species != eSpecies {
t.Errorf("want %s, have %s", eSpecies, species)
}
}
func TestLoadFile(t *testing.T) {
err := LoadFile(testFilePath)
if err != nil {
t.Fatal(err)
}
order := Get(kOrder)
species := Get(kSpecies)
name := Get(kName)
if order != fOrder {
t.Errorf("want %s, have %s", fOrder, order)
}
if species != fSpecies {
t.Errorf("want %s, have %s", fSpecies, species)
}
if name != eName {
t.Errorf("want %s, have %s", eName, name)
}
}

2
config/testdata/test.env vendored Normal file
View File

@@ -0,0 +1,2 @@
ORDER=stringiformes
SPECIES=strix aluco

View File

@@ -59,7 +59,7 @@ func NewSplitFile(outpath, errpath string, overwrite bool) (*File, error) {
if overwrite {
fl.fo, err = os.Create(outpath)
} else {
fl.fo, err = os.OpenFile(outpath, os.O_WRONLY|os.O_APPEND, 0644)
fl.fo, err = os.OpenFile(outpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
}
if err != nil {
@@ -69,7 +69,7 @@ func NewSplitFile(outpath, errpath string, overwrite bool) (*File, error) {
if overwrite {
fl.fe, err = os.Create(errpath)
} else {
fl.fe, err = os.OpenFile(errpath, os.O_WRONLY|os.O_APPEND, 0644)
fl.fe, err = os.OpenFile(errpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
}
if err != nil {

View File

@@ -277,3 +277,60 @@ func (lw *LogWriter) SetLevel(l Level) {
// Close is a no-op that satisfies the Logger interface.
func (lw *LogWriter) Close() error { return nil }
// Multi allows combining of loggers.
type Multi struct {
loggers []Logger
}
func NewMulti(loggers ...Logger) *Multi {
return &Multi{loggers: loggers}
}
func (m *Multi) Debug(actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.Debug(actor, event, attrs)
}
}
func (m *Multi) Info(actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.Info(actor, event, attrs)
}
}
func (m *Multi) Warn(actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.Warn(actor, event, attrs)
}
}
func (m *Multi) Error(actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.Error(actor, event, attrs)
}
}
func (m *Multi) Critical(actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.Critical(actor, event, attrs)
}
}
func (m *Multi) Fatal(actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.Fatal(actor, event, attrs)
}
}
func (m *Multi) FatalCode(exitcode int, actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.FatalCode(exitcode, actor, event, attrs)
}
}
func (m *Multi) FatalNoDie(actor, event string, attrs map[string]string) {
for _, l := range m.loggers {
l.FatalNoDie(actor, event, attrs)
}
}

49
rand/rand.go Normal file
View File

@@ -0,0 +1,49 @@
// Package rand contains utilities for interacting with math/rand, including
// seeding from a random sed.
package rand
import (
"crypto/rand"
"encoding/binary"
mrand "math/rand"
)
// CryptoUint64 generates a cryptographically-secure 64-bit integer.
func CryptoUint64() (uint64, error) {
bs := make([]byte, 8)
_, err := rand.Read(bs)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint64(bs), nil
}
// Seed initialises the non-cryptographic PRNG with a random,
// cryptographically secure value. This is done just as a good
// way to make this random. The returned 64-bit value is the seed.
func Seed() (uint64, error) {
seed, err := CryptoUint64()
if err != nil {
return 0, err
}
// NB: this is permitted.
mrand.Seed(int64(seed))
return seed, nil
}
// Int is a wrapper for math.Int so only one package needs to be imported.
func Int() int {
return mrand.Int()
}
// Intn is a wrapper for math.Intn so only one package needs to be imported.
func Intn(max int) int {
return mrand.Intn(max)
}
// Intn2 returns a random value between min and max, inclusive.
func Intn2(min, max int) int {
return Intn(max-min) + min
}

74
rand/rand_test.go Normal file
View File

@@ -0,0 +1,74 @@
package rand
import (
"fmt"
mrand "math/rand"
"testing"
)
func TestCryptoUint64(t *testing.T) {
n1, err := CryptoUint64()
if err != nil {
t.Fatal(err)
}
n2, err := CryptoUint64()
if err != nil {
t.Fatal(err)
}
// This has such a low chance of occurring that it's likely to be
// indicative of a bad CSPRNG.
if n1 == n2 {
t.Fatalf("repeated random uint64s: %d", n1)
}
}
func TestIntn(t *testing.T) {
expected := []int{3081, 4887, 4847, 1059, 3081}
mrand.Seed(1)
for i := 0; i < 5; i++ {
n := Intn2(1000, 5000)
if n != expected[i] {
fmt.Printf("invalid sequence at %d: expected %d, have %d", i, expected[i], n)
}
}
}
func TestSeed(t *testing.T) {
seed1, err := Seed()
if err != nil {
t.Fatal(err)
}
var seed2 uint64
n1 := Int()
tries := 0
for {
seed2, err = Seed()
if err != nil {
t.Fatal(err)
}
if seed1 != seed2 {
break
}
tries++
if tries > 3 {
t.Fatal("can't generate two unique seeds")
}
}
n2 := Int()
// Again, this not impossible, merely statistically improbably and a
// potential canary for RNG issues.
if n1 == n2 {
t.Fatalf("repeated integers fresh from two unique seeds: %d/%d -> %d",
seed1, seed2, n1)
}
}