Compare commits

..

10 Commits

Author SHA1 Message Date
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
6ae393ebf2 Have to explicitly allow darwin/amd64 in the build tag. 2020-01-16 06:19:21 -08:00
76d88c220d Add go mod, update ftime.
+ Stat_t on Darwin amd64 now uses the same struct fields
  for file times as Lunix, not the BSD ones. I need to
  follow up on this.
2020-01-16 06:13:31 -08:00
Kyle Isom
40e015373f Point to goutils-pkg. 2020-01-16 06:13:18 -08:00
50c226b726 Add parts. 2019-01-08 12:46:53 -08:00
12 changed files with 795 additions and 2 deletions

View File

@@ -7,6 +7,9 @@ Note that for packaging purposes, the goutils-pkg repo should be used: it
pins the library versions to working copies and vendors all depdencies. See
https://github.com/kisom/goutils-pkg for more details.
The goutils-pkg repo [1] has stable versions of the command line
utilities here, along with a vendored snapshot of any dependencies.
Contents:
ahash/ Provides hashes from string algorithm specifiers.
@@ -62,3 +65,5 @@ Each program should have a small README in the directory with more
information.
All code here is licensed under the MIT license.
[1] https://github.com/kisom/goutils-pkg/

View File

@@ -0,0 +1,333 @@
{
"summary": {
"title": "goutils threat model",
"owner": "K",
"description": "HTF COMPUTER DO WHAT COMPUTER DO"
},
"detail": {
"contributors": [],
"diagrams": [
{
"title": "COMPUTRONICS",
"thumbnail": "./public/content/images/thumbnail.jpg",
"id": 0,
"diagramJson": {
"cells": [
{
"type": "tm.Process",
"size": {
"width": 100,
"height": 100
},
"position": {
"x": 235,
"y": 346
},
"angle": 0,
"id": "44f457bb-4c3c-45a4-8c49-1556afb6fe2a",
"z": 1,
"hasOpenThreats": false,
"attrs": {
".element-shape": {
"class": "element-shape hasNoOpenThreats isInScope"
},
"text": {
"text": "DOING THE\nTHING"
},
".element-text": {
"class": "element-text hasNoOpenThreats isInScope"
}
}
},
{
"type": "tm.Actor",
"size": {
"width": 160,
"height": 80
},
"position": {
"x": 239,
"y": 31
},
"angle": 0,
"id": "f5cdeb80-8c89-4af1-8a5d-c23e9dda81dd",
"z": 2,
"hasOpenThreats": true,
"threats": [
{
"status": "Open",
"severity": "Medium",
"title": "Creepering",
"type": "Denial of service",
"description": "Creeping and what not.",
"mitigation": "Banishment."
}
],
"attrs": {
".element-shape": {
"class": "element-shape hasOpenThreats isInScope"
},
"text": {
"text": "the darkness"
},
".element-text": {
"class": "element-text hasOpenThreats isInScope"
}
}
},
{
"type": "tm.Store",
"size": {
"width": 160,
"height": 80
},
"position": {
"x": 413,
"y": 368
},
"angle": 0,
"id": "0132401e-9bca-497d-8a04-7bfa64f9b548",
"z": 3,
"hasOpenThreats": false,
"attrs": {
".element-shape": {
"class": "element-shape hasNoOpenThreats isInScope"
},
"text": {
"text": "crown jewels"
},
".element-text": {
"class": "element-text hasNoOpenThreats isInScope"
}
}
},
{
"type": "tm.Actor",
"size": {
"width": 160,
"height": 80
},
"position": {
"x": 384,
"y": 473
},
"angle": 0,
"id": "b2988659-754c-452d-9ef5-a2c766792a52",
"z": 4,
"hasOpenThreats": false,
"attrs": {
".element-shape": {
"class": "element-shape hasNoOpenThreats isInScope"
},
"text": {
"text": "civilians"
},
".element-text": {
"class": "element-text hasNoOpenThreats isInScope"
}
}
},
{
"type": "tm.Actor",
"size": {
"width": 160,
"height": 80
},
"position": {
"x": 390,
"y": 251
},
"angle": 0,
"id": "45ed7d67-2d9f-4a7b-a5d2-a222beb1ba02",
"z": 5,
"hasOpenThreats": false,
"attrs": {
".element-shape": {
"class": "element-shape hasNoOpenThreats isInScope"
},
"text": {
"text": "captain america"
},
".element-text": {
"class": "element-text hasNoOpenThreats isInScope"
}
}
},
{
"type": "tm.Boundary",
"smooth": true,
"source": {
"x": 70,
"y": 212
},
"target": {
"x": 773,
"y": 181
},
"vertices": [],
"id": "675f56fc-ec5a-48f1-83d8-e65bafee112c",
"z": 6,
"attrs": {}
},
{
"type": "tm.Flow",
"smooth": true,
"source": {
"id": "b2988659-754c-452d-9ef5-a2c766792a52"
},
"target": {
"id": "44f457bb-4c3c-45a4-8c49-1556afb6fe2a"
},
"vertices": [
{
"x": 389,
"y": 462
},
{
"x": 375,
"y": 459
},
{
"x": 359,
"y": 441
}
],
"id": "a841a516-7f81-41c5-a5d4-832655f5e219",
"labels": [
{
"position": 0.5,
"attrs": {
"text": {
"text": "request",
"font-weight": "400",
"font-size": "small"
}
}
}
],
"z": 7,
"hasOpenThreats": false,
"isPublicNetwork": true,
"isEncrypted": true,
"attrs": {
".marker-target": {
"class": "marker-target hasNoOpenThreats isInScope"
},
".connection": {
"class": "connection hasNoOpenThreats isInScope"
}
}
},
{
"type": "tm.Flow",
"smooth": true,
"source": {
"id": "44f457bb-4c3c-45a4-8c49-1556afb6fe2a"
},
"target": {
"id": "0132401e-9bca-497d-8a04-7bfa64f9b548"
},
"vertices": [],
"id": "f4181dae-fcad-44c7-ad98-19b5bb1b9c84",
"labels": [
{
"position": 0.5,
"attrs": {
"text": {
"text": "cogitate",
"font-weight": "400",
"font-size": "small"
}
}
}
],
"z": 8,
"hasOpenThreats": false,
"attrs": {
".marker-target": {
"class": "marker-target hasNoOpenThreats isInScope"
},
".connection": {
"class": "connection hasNoOpenThreats isInScope"
}
}
},
{
"type": "tm.Flow",
"smooth": true,
"source": {
"id": "45ed7d67-2d9f-4a7b-a5d2-a222beb1ba02"
},
"target": {
"id": "0132401e-9bca-497d-8a04-7bfa64f9b548"
},
"vertices": [],
"id": "01353067-ce16-4a19-abb7-ce47edc7e309",
"labels": [
{
"position": 0.5,
"attrs": {
"text": {
"text": "adjudicate",
"font-weight": "400",
"font-size": "small"
}
}
}
],
"z": 9,
"hasOpenThreats": false,
"attrs": {
".marker-target": {
"class": "marker-target hasNoOpenThreats isInScope"
},
".connection": {
"class": "connection hasNoOpenThreats isInScope"
}
}
},
{
"type": "tm.Flow",
"smooth": true,
"source": {
"id": "45ed7d67-2d9f-4a7b-a5d2-a222beb1ba02"
},
"target": {
"id": "44f457bb-4c3c-45a4-8c49-1556afb6fe2a"
},
"vertices": [],
"id": "5298f767-1675-4ba3-b450-e2a5aad62452",
"labels": [
{
"position": 0.5,
"attrs": {
"text": {
"text": "assimilate",
"font-weight": "400",
"font-size": "small"
}
}
}
],
"z": 10,
"hasOpenThreats": false,
"attrs": {
".marker-target": {
"class": "marker-target hasNoOpenThreats isInScope"
},
".connection": {
"class": "connection hasNoOpenThreats isInScope"
}
}
}
]
},
"size": {
"height": 590,
"width": 948.910888671875
}
}
],
"reviewer": "K"
}
}

9
cmd/parts/README Normal file
View File

@@ -0,0 +1,9 @@
parts: simple parts database for electronic components
Usage: parts [id] -- query the database for a part
parts [-c class] [id] [description] -- store a part in the database
Options:
-f path Path to parts database (default is
/home/kyle/.parts.json).

142
cmd/parts/main.go Normal file
View File

@@ -0,0 +1,142 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/kisom/goutils/die"
)
const dbVersion = "1"
var dbFile = filepath.Join(os.Getenv("HOME"), ".parts.json")
var partsDB = &database{Version: dbVersion}
type part struct {
Name string `json:"name"`
Description string `json:"description"`
Class string `json:"class,omitempty"`
}
func (p part) String() string {
return fmt.Sprintf("%s: %s", p.Name, p.Description)
}
type database struct {
Version string `json:"version"`
LastUpdate int64 `json:"json"`
Parts map[string]part `json:"parts"`
}
func help(w io.Writer) {
fmt.Fprintf(w, `Usage: parts [id] -- query the database for a part
parts [-c class] [id] [description] -- store a part in the database
Options:
-f path Path to parts database (default is
%s).
`, dbFile)
}
func loadDatabase() {
data, err := ioutil.ReadFile(dbFile)
if err != nil && os.IsNotExist(err) {
partsDB = &database{
Version: dbVersion,
Parts: map[string]part{},
}
return
}
die.If(err)
err = json.Unmarshal(data, partsDB)
die.If(err)
}
func findPart(partName string) {
partName = strings.ToLower(partName)
for name, part := range partsDB.Parts {
if strings.Contains(strings.ToLower(name), partName) {
fmt.Println(part.String())
}
}
}
func writeDB() {
data, err := json.Marshal(partsDB)
die.If(err)
err = ioutil.WriteFile(dbFile, data, 0644)
die.If(err)
}
func storePart(name, class, description string) {
p, exists := partsDB.Parts[name]
if exists {
fmt.Printf("warning: replacing part %s\n", name)
fmt.Printf("\t%s\n", p.String())
}
partsDB.Parts[name] = part{
Name: name,
Class: class,
Description: description,
}
writeDB()
}
func listParts() {
parts := make([]string, 0, len(partsDB.Parts))
for partName := range partsDB.Parts {
parts = append(parts, partName)
}
sort.Strings(parts)
for _, partName := range parts {
fmt.Println(partsDB.Parts[partName].String())
}
}
func main() {
var class string
var helpFlag bool
flag.StringVar(&class, "c", "", "device class")
flag.StringVar(&dbFile, "f", dbFile, "`path` to database")
flag.BoolVar(&helpFlag, "h", false, "Print a help message.")
flag.Parse()
if helpFlag {
help(os.Stdout)
return
}
loadDatabase()
switch flag.NArg() {
case 0:
help(os.Stdout)
return
case 1:
partName := flag.Arg(0)
if partName == "list" {
listParts()
} else {
findPart(flag.Arg(0))
}
return
default:
description := strings.Join(flag.Args()[1:], " ")
storePart(flag.Arg(0), class, description)
return
}
}

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

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/kisom/goutils
go 1.13

View File

@@ -1,4 +1,4 @@
// +build freebsd darwin netbsd
// +build freebsd darwin,386 netbsd
package lib

View File

@@ -1,4 +1,4 @@
// +build unix linux openbsd
// +build unix linux openbsd darwin,amd64
package lib

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)
}
}