diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/aoc2019/default.nix | 22 | ||||
-rw-r--r-- | tools/aoc2019/solution-day1.el | 28 | ||||
-rw-r--r-- | tools/aoc2019/solution-day2.el | 53 | ||||
-rw-r--r-- | tools/aoc2019/solution-day3.el | 58 | ||||
-rwxr-xr-x | tools/bin/__dispatch.sh | 40 | ||||
l--------- | tools/bin/aoc2019 | 1 | ||||
l--------- | tools/bin/blog_cli | 1 | ||||
l--------- | tools/bin/kontemplate | 1 | ||||
l--------- | tools/bin/pass | 1 | ||||
l--------- | tools/bin/stern | 1 | ||||
l--------- | tools/bin/terraform | 1 | ||||
-rw-r--r-- | tools/blog_cli/README.md | 41 | ||||
-rw-r--r-- | tools/blog_cli/default.nix | 9 | ||||
-rw-r--r-- | tools/blog_cli/main.go | 209 | ||||
-rw-r--r-- | tools/gotest/default.nix | 27 | ||||
-rw-r--r-- | tools/gotest/lib.go | 11 | ||||
-rw-r--r-- | tools/gotest/main.go | 16 | ||||
-rw-r--r-- | tools/gotest/test.proto | 9 | ||||
-rw-r--r-- | tools/kms_pass.nix | 60 |
19 files changed, 589 insertions, 0 deletions
diff --git a/tools/aoc2019/default.nix b/tools/aoc2019/default.nix new file mode 100644 index 000000000000..a53586eea9eb --- /dev/null +++ b/tools/aoc2019/default.nix @@ -0,0 +1,22 @@ +# Solutions for Advent of Code 2019, written in Emacs Lisp. +# +# For each day a new file is created as "solution-day$n.el". +{ pkgs, ... }: + +let + inherit (builtins) attrNames filter head listToAttrs match readDir; + dir = readDir ./.; + matchSolution = match "solution-(.*)\.el"; + isSolution = f: (matchSolution f) != null; + getDay = f: head (matchSolution f); + + solutionFiles = filter (e: dir."${e}" == "regular" && isSolution e) (attrNames dir); + solutions = map (f: let day = getDay f; in { + name = day; + value = pkgs.writeElispBin { + name = "aoc2019"; + deps = p: with p; [ dash s ht ]; + src = ./. + ("/" + f); + }; + }) solutionFiles; +in listToAttrs solutions diff --git a/tools/aoc2019/solution-day1.el b/tools/aoc2019/solution-day1.el new file mode 100644 index 000000000000..d805c22ec870 --- /dev/null +++ b/tools/aoc2019/solution-day1.el @@ -0,0 +1,28 @@ +;; Advent of Code 2019 - Day 1 +(require 'dash) + +;; Puzzle 1: + +(defvar day-1/input + '(83285 96868 121640 51455 128067 128390 141809 52325 68310 140707 124520 149678 + 87961 52040 133133 52203 117483 85643 84414 86558 65402 122692 88565 61895 + 126271 128802 140363 109764 53600 114391 98973 124467 99574 69140 144856 + 56809 149944 138738 128823 82776 77557 51994 74322 64716 114506 124074 + 73096 97066 96731 149307 135626 121413 69575 98581 50570 60754 94843 72165 + 146504 53290 63491 50936 79644 119081 70218 85849 133228 114550 131943 + 67288 68499 80512 148872 99264 119723 68295 90348 146534 52661 99146 95993 + 130363 78956 126736 82065 77227 129950 97946 132345 107137 79623 148477 + 88928 118911 75277 97162 80664 149742 88983 74518)) + +(defun calculate-fuel (mass) + (- (/ mass 3) 2)) + +(message "Solution to day1/1: %d" (apply #'+ (-map #'calculate-fuel day-1/input))) + +;; Puzzle 2: +(defun calculate-recursive-fuel (mass) + (let ((fuel (calculate-fuel mass))) + (if (< fuel 0) 0 + (+ fuel (calculate-recursive-fuel fuel))))) + +(message "Solution to day1/2: %d" (apply #'+ (-map #'calculate-recursive-fuel day-1/input))) diff --git a/tools/aoc2019/solution-day2.el b/tools/aoc2019/solution-day2.el new file mode 100644 index 000000000000..6ecac1e2016c --- /dev/null +++ b/tools/aoc2019/solution-day2.el @@ -0,0 +1,53 @@ +;; -*- lexical-binding: t; -*- +;; Advent of Code 2019 - Day 2 +(require 'dash) +(require 'ht) + +(defvar day2/input + [1 0 0 3 1 1 2 3 1 3 4 3 1 5 0 3 2 1 9 19 1 19 5 23 1 13 23 27 1 27 6 31 + 2 31 6 35 2 6 35 39 1 39 5 43 1 13 43 47 1 6 47 51 2 13 51 55 1 10 55 + 59 1 59 5 63 1 10 63 67 1 67 5 71 1 71 10 75 1 9 75 79 2 13 79 83 1 9 + 83 87 2 87 13 91 1 10 91 95 1 95 9 99 1 13 99 103 2 103 13 107 1 107 10 + 111 2 10 111 115 1 115 9 119 2 119 6 123 1 5 123 127 1 5 127 131 1 10 + 131 135 1 135 6 139 1 10 139 143 1 143 6 147 2 147 13 151 1 5 151 155 1 + 155 5 159 1 159 2 163 1 163 9 0 99 2 14 0 0]) + +;; Puzzle 1 + +(defun day2/single-op (f state idx) + (let* ((a (aref state (aref state (+ 1 idx)))) + (b (aref state (aref state (+ 2 idx)))) + (p (aref state (+ 3 idx))) + (result (funcall f a b))) + (aset state p (funcall f a b)))) + +(defun day2/operate (state idx) + (pcase (aref state idx) + (99 (aref state 0)) + (1 (day2/single-op #'+ state idx) + (day2/operate state (+ 4 idx))) + (2 (day2/single-op #'* state idx) + (day2/operate state (+ 4 idx))) + (other (error "Unknown opcode: %s" other)))) + +(defun day2/program-with-inputs (noun verb) + (let* ((input (copy-tree day2/input t))) + (aset input 1 noun) + (aset input 2 verb) + (day2/operate input 0))) + +(message "Solution to day2/1: %s" (day2/program-with-inputs 12 2)) + +;; Puzzle 2 +(let* ((used (ht)) + (noun 0) + (verb 0) + (result (day2/program-with-inputs noun verb))) + (while (/= 19690720 result) + (setq noun (random 100)) + (setq verb (random 100)) + (unless (ht-get used (format "%d%d" noun verb)) + (ht-set used (format "%d%d" noun verb) t) + (setq result (day2/program-with-inputs noun verb)))) + + (message "Solution to day2/2: %s%s" noun verb)) diff --git a/tools/aoc2019/solution-day3.el b/tools/aoc2019/solution-day3.el new file mode 100644 index 000000000000..c0d2eb5ee657 --- /dev/null +++ b/tools/aoc2019/solution-day3.el @@ -0,0 +1,58 @@ +;; -*- lexical-binding: t; -*- +;; Advent of Code 2019 - Day 3 +;; +;; Note: Input was pre-processed with some Emacs shortcuts. +(require 'cl) +(require 'dash) +(require 'ht) +(require 's) + +(defvar day3/input/wire1 + "R1010,D422,L354,U494,L686,U894,R212,U777,L216,U9,L374,U77,R947,U385,L170,U916,R492,D553,L992,D890,L531,U360,R128,U653,L362,U522,R817,U198,L126,D629,L569,U300,L241,U145,R889,D196,L450,D576,L319,D147,R985,U889,L941,U837,L608,D77,L864,U911,L270,D869,R771,U132,L249,U603,L36,D328,L597,U992,L733,D370,L947,D595,L308,U536,L145,U318,R55,D773,R175,D505,R483,D13,R780,U778,R445,D107,R490,U245,L587,U502,R446,U639,R150,U35,L455,D522,R866,U858,R394,D975,R513,D378,R58,D646,L374,D675,R209,U228,R530,U543,L480,U677,L912,D164,L573,U587,L784,D626,L994,U250,L215,U985,R684,D79,L877,U811,L766,U617,L665,D246,L408,U800,L360,D272,L436,U138,R240,U735,L681,U68,L608,D59,R532,D808,L104,U968,R887,U819,R346,U698,L317,U582,R516,U55,L303,U607,L457,U479,L510,D366,L583,U519,R878,D195,R970,D267,R842,U784,R9,D946,R833,D238,L232,D94,L860,D47,L346,U951,R491,D745,R849,U273,R263,U392,L341,D808,R696,U326,R886,D296,L865,U833,R241,U644,R729,D216,R661,D712,L466,D699,L738,U5,L556,D693,R912,D13,R48,U63,L877,U628,L689,D929,R74,U924,R612,U153,R417,U425,L879,D378,R79,D248,L3,U519,R366,U281,R439,D823,R149,D668,R326,D342,L213,D735,R504,U265,L718,D842,L565,U105,L214,U963,R518,D681,R642,U170,L111,U6,R697,U572,R18,U331,L618,D255,R534,D322,L399,U595,L246,U651,L836,U757,R417,D795,R291,U759,L568,U965,R828,D570,R350,U317,R338,D173,L74,D833,L650,D844,L70,U913,R594,U407,R674,D684,L481,D564,L128,D277,R851,D274,L435,D582,R469,U729,R387,D818,R443,U504,R414,U8,L842,U845,R275,U986,R53,U660,R661,D225,R614,U159,R477") + +(defvar day3/input/wire2 + "L1010,D698,R442,U660,L719,U702,L456,D86,R938,D177,L835,D639,R166,D285,L694,U468,L569,D104,L234,D574,L669,U299,L124,D275,L179,D519,R617,U72,L985,D248,R257,D276,L759,D834,R490,U864,L406,U181,R911,U873,R261,D864,R260,U759,R648,U158,R308,D386,L835,D27,L745,U91,R840,U707,R275,U543,L663,U736,L617,D699,R924,U103,R225,U455,R708,U319,R569,U38,R315,D432,L179,D975,R519,D546,L295,U680,L685,U603,R262,D250,R7,U171,R261,U519,L832,U534,L471,U431,L474,U886,R10,D179,L79,D555,R452,U452,L832,U863,L367,U538,L237,D160,R441,U605,R942,U259,L811,D552,R646,D353,L225,D94,L35,D307,R752,U23,R698,U610,L379,D932,R698,D751,R178,D347,R325,D156,R471,D555,R558,D593,R773,U2,L955,U764,L735,U438,R364,D640,L757,U534,R919,U409,R361,U407,R336,D808,R877,D648,R610,U198,R340,U94,R795,D667,R811,U975,L965,D224,R565,D681,L64,U567,R621,U922,L665,U329,R242,U592,L727,D481,L339,U402,R213,D280,R656,U169,R976,D962,L294,D505,L251,D689,L497,U133,R230,D441,L90,D220,L896,D657,L500,U331,R502,U723,R762,D613,L447,D256,L226,U309,L935,U384,L740,D459,R309,D707,R952,D747,L304,D105,R977,D539,R941,D21,R291,U216,R132,D543,R515,U453,L854,D42,R982,U102,L469,D639,R559,D68,R302,U734,R980,D214,R107,D191,L730,D793,L63,U17,R807,U196,R412,D592,R330,D941,L87,D291,L44,D94,L272,D780,R968,U837,L712,D704,R163,U981,R537,U778,R220,D303,L196,D951,R163,D446,R11,D623,L72,D778,L158,U660,L189,D510,L247,D716,L89,U887,L115,U114,L36,U81,R927,U293,L265,U183,R331,D267,R745,D298,L561,D918,R299,U810,L322,U679,L739,D854,L581,U34,L862,D779,R23") + +;; Puzzle 1 + +(defun wire-from (raw) + (-map (lambda (s) + (cons (substring s 0 1) (string-to-number (substring s 1)))) + (s-split "," raw))) + +(defun day3/move (x y next) + (cl-flet ((steps (by op) + (-map op (reverse (number-sequence 1 by))))) + (pcase next + (`("L" . ,by) (steps by (lambda (n) (cons (- x n) y)))) + (`("R" . ,by) (steps by (lambda (n) (cons (+ x n) y)))) + (`("U" . ,by) (steps by (lambda (n) (cons x (+ y n))))) + (`("D" . ,by) (steps by (lambda (n) (cons x (- y n)))))))) + +(defun day3/wire-points (wire) + (let ((points (ht)) + (point-list (-reduce-from + (lambda (acc point) + (-let* (((x . y) (car acc)) + (next (day3/move x y point))) + (-concat next acc))) + '((0 . 0)) wire))) + (-map (lambda (p) (ht-set! points p t)) point-list) + (ht-remove! points '(0 . 0)) + points)) + +(defun day3/closest-intersection (wire1 wire2) + (let* ((wire1-points (day3/wire-points (wire-from wire1))) + (wire2-points (day3/wire-points (wire-from wire2))) + (crossed-points (-filter (lambda (p) (ht-contains? wire1-points p)) + (ht-keys wire2-points)))) + + (car (-sort #'< + (-map (-lambda ((x . y)) + (+ (abs x) (abs y))) + crossed-points))))) + +(message "Solution form day3/1: %d" + (day3/closest-intersection day3/input/wire1 + day3/input/wire2)) + diff --git a/tools/bin/__dispatch.sh b/tools/bin/__dispatch.sh new file mode 100755 index 000000000000..fb8a4d779185 --- /dev/null +++ b/tools/bin/__dispatch.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# This script dispatches invocations transparently to programs instantiated from +# Nix. +# +# To add a new tool, insert it into the case statement below by setting `attr` +# to the key in nixpkgs which represents the program you want to run. +set -ueo pipefail + +readonly REPO_ROOT=$(git rev-parse --show-toplevel) +readonly TARGET_TOOL=$(basename $0) + +case "${TARGET_TOOL}" in + terraform) + attr="third_party.terraform-gcp" + ;; + kontemplate) + attr="kontemplate" + ;; + blog_cli) + attr="tools.blog_cli" + ;; + stern) + attr="stern" + ;; + pass) + attr="tools.kms_pass" + ;; + aoc2019) + attr="tools.aoc2019.${1}" + ;; + *) + echo "The tool '${TARGET_TOOL}' is currently not installed in this repository." + exit 1 + ;; +esac + +result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}") +PATH="${result}/bin:$PATH" + +exec "${TARGET_TOOL}" "${@}" diff --git a/tools/bin/aoc2019 b/tools/bin/aoc2019 new file mode 120000 index 000000000000..8390ec9c9652 --- /dev/null +++ b/tools/bin/aoc2019 @@ -0,0 +1 @@ +__dispatch.sh \ No newline at end of file diff --git a/tools/bin/blog_cli b/tools/bin/blog_cli new file mode 120000 index 000000000000..8390ec9c9652 --- /dev/null +++ b/tools/bin/blog_cli @@ -0,0 +1 @@ +__dispatch.sh \ No newline at end of file diff --git a/tools/bin/kontemplate b/tools/bin/kontemplate new file mode 120000 index 000000000000..8390ec9c9652 --- /dev/null +++ b/tools/bin/kontemplate @@ -0,0 +1 @@ +__dispatch.sh \ No newline at end of file diff --git a/tools/bin/pass b/tools/bin/pass new file mode 120000 index 000000000000..8390ec9c9652 --- /dev/null +++ b/tools/bin/pass @@ -0,0 +1 @@ +__dispatch.sh \ No newline at end of file diff --git a/tools/bin/stern b/tools/bin/stern new file mode 120000 index 000000000000..8390ec9c9652 --- /dev/null +++ b/tools/bin/stern @@ -0,0 +1 @@ +__dispatch.sh \ No newline at end of file diff --git a/tools/bin/terraform b/tools/bin/terraform new file mode 120000 index 000000000000..8390ec9c9652 --- /dev/null +++ b/tools/bin/terraform @@ -0,0 +1 @@ +__dispatch.sh \ No newline at end of file diff --git a/tools/blog_cli/README.md b/tools/blog_cli/README.md new file mode 100644 index 000000000000..7afa0fe9207a --- /dev/null +++ b/tools/blog_cli/README.md @@ -0,0 +1,41 @@ +tazblog CLI +=========== + +My blog stores its content in DNS, spread out over three types of `TXT` entries: + +* `TXT _posts.blog.tazj.in.`: A sorted list of posts, serialised as a JSON list of + strings (e.g. `["1486830338", "1476807384"]`) + +* `TXT _chunks.$postID.blog.tazj.in`: JSON chunks containing the blog post text + +* `TXT _meta.$postID.blog.tazj.in`: JSON blob with blog post metadata + +All JSON blobs are base64-encoded. + +This CLI tool helps to update those records. + +Each blog post data is a series of JSON-encoded structures which follow one of +these formats: + +``` +struct metadata { + chunks: int + title: string + date: date +} +``` + +Where `chunks` describes the number of chunks following this format: + +``` +struct chunk { + c: int + t: string +} +``` + +Writing a blog post to DNS means taking its text and metadata, chunking it up +and writing the chunks. + +Reading a blog post means retrieving all data, reading the metadata and then +assembling the chunks in order. diff --git a/tools/blog_cli/default.nix b/tools/blog_cli/default.nix new file mode 100644 index 000000000000..c22e4c949bc1 --- /dev/null +++ b/tools/blog_cli/default.nix @@ -0,0 +1,9 @@ +{ pkgs, ... }: + +pkgs.buildGo.program { + name = "blog_cli"; + srcs = [ ./main.go ]; + deps = with pkgs.third_party; [ + gopkgs."google.golang.org".api.dns.v1.gopkg + ]; +} // { meta.enableCI = true; } diff --git a/tools/blog_cli/main.go b/tools/blog_cli/main.go new file mode 100644 index 000000000000..db64f8378e40 --- /dev/null +++ b/tools/blog_cli/main.go @@ -0,0 +1,209 @@ +// The tazblog CLI implements updating my blog records in DNS, see the +// README in this folder for details. +// +// The post input format is a file with the title on one line, +// followed by the date on a line, followed by an empty line, followed +// by the post text. +package main + +import ( + "context" + "encoding/base64" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "time" + + "google.golang.org/api/dns/v1" +) + +var ( + project = flag.String("project", "tazjins-infrastructure", "Target GCP project") + zone = flag.String("zone", "blog-tazj-in", "Target Cloud DNS zone") + title = flag.String("title", "", "Title of the blog post") + date = flag.String("date", "", "Date the post was written on") + infile = flag.String("text", "", "Text file containing the blog post") + id = flag.String("id", "", "Post ID - will be generated if unset") +) + +// Number of runes to include in a single chunk. If any chunks exceed +// the limit of what can be encoded, the chunk size is reduced and we +// try again. +var chunkSize = 200 + +type day time.Time + +func (d day) MarshalJSON() ([]byte, error) { + j := (time.Time(d)).Format(`"2006-01-02"`) + return []byte(j), nil +} + +type metadata struct { + Chunks int `json:"c"` + Title string `json:"t"` + Date day `json:"d"` +} + +type chunk struct { + Chunk int + Text string +} + +type post struct { + ID string + Meta metadata + Chunks []string +} + +func (p *post) writeToDNS() error { + var additions []*dns.ResourceRecordSet + additions = append(additions, &dns.ResourceRecordSet{ + Name: fmt.Sprintf("_meta.%s.blog.tazj.in.", p.ID), + Type: "TXT", + Ttl: 1200, + Rrdatas: []string{ + encodeJSON(p.Meta), + }, + }) + + for i, c := range p.Chunks { + additions = append(additions, &dns.ResourceRecordSet{ + Name: fmt.Sprintf("_%v.%s.blog.tazj.in.", i, p.ID), + Type: "TXT", + Ttl: 1200, + Rrdatas: []string{c}, + }) + } + + ctx := context.Background() + dnsSvc, err := dns.NewService(ctx) + if err != nil { + return err + } + + change := dns.Change{ + Additions: additions, + } + + _, err = dnsSvc.Changes.Create(*project, *zone, &change).Do() + if err != nil { + return err + } + + return nil +} + +// Encode given value as JSON and base64-encode it. +func encodeJSON(v interface{}) string { + outer, err := json.Marshal(v) + if err != nil { + log.Fatalln("Failed to encode JSON", err) + } + + return base64.RawStdEncoding.EncodeToString(outer) +} + +// Encode a chunk and check whether it is too large +func encodeChunk(c chunk) (string, bool) { + tooLarge := false + s := base64.RawStdEncoding.EncodeToString([]byte(c.Text)) + + if len(s) >= 255 { + tooLarge = true + } + + return s, tooLarge +} + +func createPost(id, title, text string, date day) post { + runes := []rune(text) + n := 0 + tooLarge := false + + var chunks []string + + for chunkSize < len(runes) { + c, l := encodeChunk(chunk{ + Chunk: n, + Text: string(runes[0:chunkSize:chunkSize]), + }) + + tooLarge = tooLarge || l + chunks = append(chunks, c) + runes = runes[chunkSize:] + n++ + } + + if len(runes) > 0 { + c, l := encodeChunk(chunk{ + Chunk: n, + Text: string(runes), + }) + + tooLarge = tooLarge || l + chunks = append(chunks, c) + n++ + } + + if tooLarge { + log.Println("Too large at chunk size", chunkSize) + chunkSize -= 5 + return createPost(id, title, text, date) + } + + return post{ + ID: id, + Meta: metadata{ + Chunks: n, + Title: title, + Date: date, + }, + Chunks: chunks, + } +} + +func main() { + flag.Parse() + + if *title == "" { + log.Fatalln("Post title must be set (-title)") + } + + if *infile == "" { + log.Fatalln("Post text file must be set (-text)") + } + + if *id == "" { + log.Fatalln("Post ID must be set (-id)") + } + + var postDate day + if *date != "" { + t, err := time.Parse("2006-01-02", *date) + if err != nil { + log.Fatalln("Invalid post date", err) + } + + postDate = day(t) + } else { + postDate = day(time.Now()) + } + + t, err := ioutil.ReadFile(*infile) + if err != nil { + log.Fatalln("Failed to read post:", err) + } + + post := createPost(*id, *title, string(t), postDate) + + log.Println("Writing post to DNS ...") + err = post.writeToDNS() + + if err != nil { + log.Fatalln("Failed to write post:", err) + } + + log.Println("Successfully wrote entries") +} diff --git a/tools/gotest/default.nix b/tools/gotest/default.nix new file mode 100644 index 000000000000..168d15748e1f --- /dev/null +++ b/tools/gotest/default.nix @@ -0,0 +1,27 @@ +# This file demonstrates how to make use of pkgs.buildGo. +# +# It introduces libraries and protobuf support, however gRPC support +# is not yet included. +# +# From the root of this repository this example can be built with +# `nix-build -A tools.gotest` +{ pkgs, ... }: + +let + inherit (pkgs) buildGo; + + somelib = buildGo.package { + name = "somelib"; + srcs = [ ./lib.go ]; + }; + + someproto = buildGo.proto { + name = "someproto"; + proto = ./test.proto; + }; + +in buildGo.program { + name = "gotest"; + srcs = [ ./main.go ]; + deps = [ somelib someproto ]; +} // { meta.enableCI = true; } diff --git a/tools/gotest/lib.go b/tools/gotest/lib.go new file mode 100644 index 000000000000..0aeebb2aea69 --- /dev/null +++ b/tools/gotest/lib.go @@ -0,0 +1,11 @@ +package somelib + +import "fmt" + +func Name() string { + return "edef" +} + +func Greet(s string) string { + return fmt.Sprintf("Hello %s", s) +} diff --git a/tools/gotest/main.go b/tools/gotest/main.go new file mode 100644 index 000000000000..99218c077617 --- /dev/null +++ b/tools/gotest/main.go @@ -0,0 +1,16 @@ +// This program just exists to import some libraries and demonstrate +// that the build works, it doesn't do anything useful. +package main + +import ( + "fmt" + "somelib" + "someproto" +) + +func main() { + p := someproto.Person{ + Name: somelib.Name(), + } + fmt.Println(somelib.Greet(fmt.Sprintf("%v", p))) +} diff --git a/tools/gotest/test.proto b/tools/gotest/test.proto new file mode 100644 index 000000000000..76af63072be3 --- /dev/null +++ b/tools/gotest/test.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +package someproto; + +import "google/protobuf/timestamp.proto"; + +message Person { + string name = 1; + google.protobuf.Timestamp last_updated = 2; +} diff --git a/tools/kms_pass.nix b/tools/kms_pass.nix new file mode 100644 index 000000000000..14989b392dd1 --- /dev/null +++ b/tools/kms_pass.nix @@ -0,0 +1,60 @@ +# This tool mimics a subset of the interface of 'pass', but uses +# Google Cloud KMS for encryption. +# +# It is intended to be compatible with how 'kontemplate' invokes +# 'pass.' +# +# Only the 'show' and 'insert' commands are supported. + +{ pkgs, kms, ... }: + +let inherit (pkgs.third_party) google-cloud-sdk tree writeShellScriptBin; +in (writeShellScriptBin "pass" '' + set -eo pipefail + + CMD="$1" + readonly SECRET=$2 + readonly SECRET_PATH="$SECRETS_DIR/$SECRET" + + function secret_check { + if [[ -z $SECRET ]]; then + echo 'Secret must be specified' + exit 1 + fi + } + + if [[ -z $CMD ]]; then + CMD="ls" + fi + + case "$CMD" in + ls) + ${tree}/bin/tree $SECRETS_DIR + ;; + show) + secret_check + ${google-cloud-sdk}/bin/gcloud kms decrypt \ + --project ${kms.project} \ + --location ${kms.region} \ + --keyring ${kms.keyring} \ + --key ${kms.key} \ + --ciphertext-file $SECRET_PATH \ + --plaintext-file - + ;; + insert) + secret_check + ${google-cloud-sdk}/bin/gcloud kms encrypt \ + --project ${kms.project} \ + --location ${kms.region} \ + --keyring ${kms.keyring} \ + --key ${kms.key} \ + --ciphertext-file $SECRET_PATH \ + --plaintext-file - + echo "Inserted secret '$SECRET'" + ;; + *) + echo "Usage: pass show/insert <secret>" + exit 1 + ;; + esac +'') // { meta.enableCI = true; } |