Skip to content
This repository was archived by the owner on Nov 17, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

VERSION ?= dev-$(shell date +%FT%T%z)
VERSION ?= eip-$(shell date +%FT%T%z)

GOOS?=darwin
GOARCH?=arm64

GO ?= go
GO_FLAGS ?= -mod=vendor
GO_LDFLAGS ?=
GO_TESTFLAGS ?= -race
GO_BUILDFLAGS ?= -tags netgo -installsuffix netgo -ldflags="-X main.version=$(VERSION) $(GO_LDFLAGS)"
GO_BUILDFLAGS ?= -tags netgo -installsuffix netgo -ldflags="-X main.version=$(VERSION) $(GO_LDFLAGS)" -o kubecfg.${GOOS}.${GOARCH}
GOFMT ?= gofmt
# GINKGO = "go test" also works if you want to avoid ginkgo tool
GINKGO ?= ginkgo
Expand All @@ -37,7 +40,7 @@ KUBECONFIG ?= $(HOME)/.kube/config
all: kubecfg

kubecfg:
CGO_ENABLED=0 $(GO) build $(GO_FLAGS) $(GO_BUILDFLAGS) .
export GOARCH=$(GOARCH); GOOS=$(GOOS) CGO_ENABLED=0 $(GO) build $(GO_FLAGS) $(GO_BUILDFLAGS) .

generate:
$(GO) generate -x $(GO_FLAGS) $(GO_PACKAGES)
Expand Down
208 changes: 208 additions & 0 deletions examples/1password/1password.libsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright 2019 The kubecfg authors
//
//
// 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.


// Example and reusable library to work with 1Password to store and retrieve passwords.
// Makes use of 'kubecfg.libsonnet' native functions:
// - generatePassword
// - execProgram
// - ntHashFromPassword

local kubecfg = import "kubecfg.libsonnet";
local lengthDefault = 16;
local numDigitsDefault = 4;
local numSymbolsDefault = 4;
local noUpperDefault = false;
local allowRepeatDefault = true;
local customSymbolsDefault = ""; // "" causes library to use the default symbol set
local fallbackDefault = "default";

{
generatePassword(field, length = lengthDefault, numDigits = numDigitsDefault, numSymbols = numSymbolsDefault,
noUpper = noUpperDefault, allowRepeat = allowRepeatDefault, customSymbols = customSymbolsDefault):: (
kubecfg.generatePassword(length, numDigits, numSymbols, noUpper, allowRepeat, customSymbols)
),

getPasswordFrom1Password(onePasswordItemName, vault, fallbackValue = fallbackDefault, useFallbackValue = false):: (
local item = $._get1PasswordItemByName(onePasswordItemName, vault);
std.trace
(
if useFallbackValue then "Returning fallback password for item named %s" % onePasswordItemName
else
if item == null then "Could not retrieve item %s from 1Password (probably not signed-in)" % onePasswordItemName
else "Item retrieved from 1Password for item named %s" % onePasswordItemName,

if useFallbackValue then fallbackValue
else
if item == null then error 'Failed to read from 1Password.'
else item.details.password
)
),

getItemFrom1Password(onePasswordItemName, vault, fallbackValue = fallbackDefault, useFallbackValue = false):: (
local item = $._get1PasswordItemByName(onePasswordItemName, vault);
std.trace
(
if useFallbackValue then "Returning fallback object for item named %s" % onePasswordItemName
else
if item == null then "Could not retrieve item %s from 1Password (probably not signed-in)" % onePasswordItemName
else "Item retrieved from 1Password for item named %s" % onePasswordItemName,

if useFallbackValue then fallbackValue
else
if item == null then {}
else item
)
),

ntHashFromPassword(password):: (
kubecfg.ntHashFromPassword(password)
),

_get1PasswordItemByName(name, vault):: (
local itemString = kubecfg.execProgram("op", "get item %s --vault=%s" % [name, vault], false);
if itemString == "" then null
else std.parseJson(itemString)
),

_generateSecrets(passwordsSpec):: {
[key]: (
local v = passwordsSpec[key];
if std.type(v) == "object" then
// use object fields as params to generate a password
$.generatePassword(key, if "length" in v then v.length else lengthDefault,
if "numDigits" in v then v.numDigits else numDigitsDefault,
if "numSymbols" in v then v.numSymbols else numSymbolsDefault,
if "noUpper" in v then v.noUpper else noUpperDefault,
if "allowRepeat" in v then v.allowRepeat else allowRepeatDefault,
"!@#$%^&*()_+`-={}|[]?,.",
//"._+:@%/-", // reduced set, should be safe for passing in shell without escaping
)
else
// just use the value verbatim
v
)
for key in std.objectFields(passwordsSpec)
},

_generateSecretsFromFallbackValues(passwordsSpec):: {
[key]: (
local v = passwordsSpec[key];
if std.type(v) == "object" then
if "fallback" in v then v.fallback else fallbackDefault
else
// just use the value verbatim
v
)
for key in std.objectFields(passwordsSpec)
},

_saveTo1Password(name, vault, stringData):: (
local item = {
fields: [],
sections: [
{
name: "kubecfg",
title: "kubecfg",
fields: [
{
k: "concealed",
n: key,
t: key,
v: stringData[key],
},
for key in std.objectFields(stringData)
]
}
],
passwordHistory: [],
notesPlain: ""
};

// trigger side effect and return stringData
local creationResult = $._createItemIn1Password(name, vault, item);
std.trace
(
if creationResult == "" then "No item created in 1Password (probably not signed-in)"
else "Created item named %s in vault %s: %s" % [name, vault, creationResult],
stringData
)
),

_createItemIn1Password(name, vault, item):: (
local encodedItem = kubecfg.encodeBase64Url(std.toString(item));
kubecfg.execProgram("op", "create item 'secure note' %s --title=%s --tags=kubecfg --vault=%s" % [encodedItem,name, vault], false)
),

_getKubecfgFieldsFrom1PasswordItem(onePasswordItem):: {
["fields"]: s.fields for s in onePasswordItem.details.sections if s.name == "kubecfg"
},

_convert1PasswordItemToSecrets(onePasswordItem):: {
[f.n]: f.v for f in $._getKubecfgFieldsFrom1PasswordItem(onePasswordItem).fields
},

// This should be considered a workaround.
//
// Converts a Jsonnet object to string and back.
// This is useful to make sure the new object only contains values and no references to functions
// (e.g. functions like generatePassword that would otherwise be evaluated every time the field value is accessed)
// see "Jsonnet Doesn't Re-use Intermediate Results" at https://databricks.com/blog/2018/10/12/writing-a-faster-jsonnet-compiler.html
_serializeObject(object):: (
std.parseJson(std.manifestJsonEx(object, " "))
),

OnePasswordSecret(name, namespace, vault, useFallbackValues = false): $._Object("v1", "Secret", name, namespace) {
local onePasswordItemName = namespace + "-" + name,
local onePasswordItem = $._get1PasswordItemByName(onePasswordItemName, vault),

// Set the value of this field for the passwords that should be generated.
// See 'secrets.jsonnet' example.
generatedPasswords_:: {},

type: "Opaque",
stringData: if useFallbackValues then
std.trace
(
"Using fallback values as requested",
$._generateSecretsFromFallbackValues(self.generatedPasswords_)
)
else
if onePasswordItem == null
then $._saveTo1Password(onePasswordItemName, vault, $._serializeObject($._generateSecrets(self.generatedPasswords_)))
else $._convert1PasswordItemToSecrets(onePasswordItem),
},


// The following is taken from https://github.com/bitnami-labs/kube-libsonnet/blob/master/kube.libsonnet
// When kube.libsonnet is used OnePasswordSecret may reference it directly.
safeName(s):: (
local length = std.length(s);
local name = (if length > 63 then std.substr(s, 0, 62) else s);
std.asciiLower(std.join("-", std.split(name, ".")))
),

_Object(apiVersion, kind, name, namespace = null):: {
local this = self,
apiVersion: apiVersion,
kind: kind,
metadata: {
name: $.safeName(name),
[if namespace != null then "namespace"]: namespace,
labels: { name: std.join("-", std.split(this.metadata.name, ":")) },
annotations: {},
},
},
}
98 changes: 98 additions & 0 deletions examples/1password/secrets.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2019 The kubecfg authors
//
//
// 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.

// Run make kubecfg
// Run ./kubecfg show examples/1password/secrets.jsonnet

local kubecfg = import "kubecfg.libsonnet";
local onePassword = import "1password.libsonnet";

function(namespace="test", useFallbackValues = false) {
local clusterName = "eip3",

// this is determining the item name (in 1Password) based on a name and namespace
// and the vault name based on a cluster name
secrets: onePassword.OnePasswordSecret("secrets", namespace, "cluster-" + clusterName, useFallbackValues) {
local secret = self,

stringData+: {
// passwords can be supplied verbatim - these will not be saved in 1Password
"verbatim-unsaved": "pass123!@#$%^&*()_+`-={}|[]?,.",

// passwords can be generated directly using a function - these will not be saved in 1Password
"generated-with-kubecfg-libsonnet": kubecfg.generatePassword(8, 2, 2, false, true, ""),

// passwords can be retrieved from a 1Password 'password' item - these will not be saved in 1Password since they already are
"existing-password-stored-in-one-password": onePassword.getPasswordFrom1Password("secret-password", "cluster-" + clusterName),

// a fallback value can be supplied in case the vault should not be accessed at all
"existing-password-stored-in-one-password-with-fallback": onePassword.getPasswordFrom1Password("secret-password", "cluster-" + clusterName, "fallback-value-for-secret-password", useFallbackValues),

// items can be retrieved verbatim from a 1Password item - these will not be saved in 1Password since they already are
[if !useFallbackValues then "existing-item-stored-in-one-password"]: onePassword.getItemFrom1Password("secret-item", "cluster-" + clusterName).details.fields[1].value,

// a fallback value (an object) can be supplied in case the vault should not be accessed at all
"existing-item-stored-in-one-password-with-fallback": onePassword.getItemFrom1Password("secret-item", "cluster-" + clusterName,
{
details: {
fields: [
{
value: "fallback-user-value",
},
{
value: "fallback-password-value",
},
],
},
},
useFallbackValues).details.fields[1].value,

// a password hash can be calculated from a password
// the password to use can be a generated one (see below) - these will not be saved in 1Password (they are recalculated every time)
"nthash-1": kubecfg.ntHashFromPassword(secret.stringData["password-1-generated-with-spec"]),
"nthash-2": onePassword.ntHashFromPassword(secret.stringData["password-2-generated-with-spec"])
},

// passwords can be generated (with different options applied when generating) - these will be saved in 1Password
generatedPasswords_+: {
"password-1-generated-with-spec": {
length: 16,
numDigits: 4,
numSymbols: 6,
noUpper: false,
allowRepeat: true,
fallback: "abc",
},
"password-2-generated-with-spec": {
length: 32,
numDigits: 1,
numSymbols: 6,
noUpper: false,
allowRepeat: false,
fallback: "def",
},
// defaults will be applied when no explicit values are supplied
"password-3-generated-with-spec": {
length: 64,
},
// defaults will be applied when no explicit values are supplied.
"password-4-generated-with-spec": {},

// passwords can be supplied verbatim - these will be saved in 1Password
"verbatim-saved": "pass456!@#$%^&*()_+`-={}|[]?,.",
},

},
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ require (
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200923155610-8b5066479488
k8s.io/kubectl v0.19.3
github.com/sethvargo/go-password v0.1.2
github.com/mattn/go-shellwords v1.0.6
github.com/DaKnOb/ntlm v0.0.0-20180331151128-c6369eea43f9
)

go 1.13
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -685,3 +685,9 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
github.com/DaKnOb/ntlm v0.0.0-20180331151128-c6369eea43f9 h1:hsrg/qzkF/EObjea7sW6jF0mxU3fRsrNiof6eV/3nZQ=
github.com/DaKnOb/ntlm v0.0.0-20180331151128-c6369eea43f9/go.mod h1:FERH8H1CSOI9WlH4vO+Qyrfa9YWBKa64nkltRo6DGNg=
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/sethvargo/go-password v0.1.2 h1:fhBF4thiPVKEZ7R6+CX46GWJiPyCyXshbeqZ7lqEeYo=
github.com/sethvargo/go-password v0.1.2/go.mod h1:qKHfdSjT26DpHQWHWWR5+X4BI45jT31dg6j4RI2TEb0=
15 changes: 15 additions & 0 deletions lib/kubecfg.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,19 @@
// to refer to submatches. Regex is as implemented in golang regexp
// package (python-ish).
regexSubst:: std.native("regexSubst"),

// generatePassword(length, numDigits, numSymbols, noUpper, allowRepeat,customSymbols):
// Return a generated password with the features specified by the parameters.
generatePassword:: std.native("generatePassword"),

// execProgram(name, arguments, failOnError): Executes a program and returns
// the result / string that was written to stdout. Program must
// be in PATH.
execProgram:: std.native("execProgram"),

// ntHashFromPassword(password): Return the NT(LM) hash for a password.
ntHashFromPassword:: std.native("ntHashFromPassword"),

// encodeBase64Url(text): Return base64-url-encoded string for text.
encodeBase64Url:: std.native("encodeBase64Url"),
}
Loading