diff --git a/Makefile b/Makefile index b610f7bc..7f8aaaf3 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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) diff --git a/examples/1password/1password.libsonnet b/examples/1password/1password.libsonnet new file mode 100644 index 00000000..7f9ed112 --- /dev/null +++ b/examples/1password/1password.libsonnet @@ -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: {}, + }, + }, +} \ No newline at end of file diff --git a/examples/1password/secrets.jsonnet b/examples/1password/secrets.jsonnet new file mode 100644 index 00000000..3254d336 --- /dev/null +++ b/examples/1password/secrets.jsonnet @@ -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!@#$%^&*()_+`-={}|[]?,.", + }, + + }, +} diff --git a/go.mod b/go.mod index 4025f089..e4915914 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index fbe4ac48..eccc1a25 100644 --- a/go.sum +++ b/go.sum @@ -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= \ No newline at end of file diff --git a/lib/kubecfg.libsonnet b/lib/kubecfg.libsonnet index 77f60024..1d069463 100644 --- a/lib/kubecfg.libsonnet +++ b/lib/kubecfg.libsonnet @@ -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"), } diff --git a/utils/bindata.go b/utils/bindata.go index e9835e46..105ff99a 100644 --- a/utils/bindata.go +++ b/utils/bindata.go @@ -66,7 +66,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _libKubecfgLibsonnet = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x55\x51\x8f\xdb\x36\x13\x7c\xf7\xaf\x18\x18\xdf\x83\x1d\x28\x56\x12\x7c\x40\x01\x17\x01\xea\x26\x29\xea\xf4\x62\xa3\xf6\xa5\xc1\xbd\x79\x4d\xad\x24\xe6\x28\x52\x25\x29\xfb\x8c\xa2\xff\xbd\x20\x25\x9d\xe5\xf3\x1d\x10\xe0\x20\x9c\xb9\xbb\xc3\x99\xd9\xd5\x2a\x4d\xf1\xc1\xd4\x27\x2b\x8b\xd2\xe3\xdd\x9b\xb7\x3f\xe1\xb6\x64\xdc\x37\x7b\x16\x79\x01\x6a\x7c\x69\xac\x1b\xa5\x69\xfb\x07\x00\x37\x52\xb0\x76\x9c\xa1\xd1\x19\x5b\xf8\x92\xb1\xa8\x49\x94\xdc\x47\x12\xfc\xc5\xd6\x49\xa3\xf1\x6e\xf6\x06\x93\x90\x30\xee\x42\xe3\xe9\xcf\x1d\xca\xc9\x34\xa8\xe8\x04\x6d\x3c\x1a\xc7\xf0\xa5\x74\xc8\xa5\x62\xf0\x83\xe0\xda\x43\x6a\x08\x53\xd5\x4a\x92\x16\x8c\xa3\xf4\x65\xbc\xaa\x03\x9a\x75\x30\x77\x1d\x8c\xd9\x7b\x92\x1a\x04\x61\xea\x13\x4c\x3e\xcc\x05\xf9\x33\x7b\xa0\xf4\xbe\x9e\xa7\xe9\xf1\x78\x9c\x51\xe4\x3d\x33\xb6\x48\x55\x9b\xeb\xd2\x9b\xe5\x87\x4f\xab\xed\xa7\xd7\xef\x66\x6f\xce\x55\x5f\xb5\x62\xe7\x60\xf9\xef\x46\x5a\xce\xb0\x3f\x81\xea\x5a\x49\x41\x7b\xc5\x50\x74\x84\xb1\xa0\xc2\x32\x67\xf0\x26\x70\x3f\x5a\xe9\xa5\x2e\x12\x38\x93\xfb\x23\x59\xee\x90\x32\xe9\xbc\x95\xfb\xc6\x5f\x18\xd8\x33\x95\xee\x22\xc1\x68\x90\xc6\x78\xb1\xc5\x72\x3b\xc6\xaf\x8b\xed\x72\x9b\x74\x38\xdf\x96\xb7\xbf\xaf\xbf\xde\xe2\xdb\x62\xb3\x59\xac\x6e\x97\x9f\xb6\x58\x6f\xf0\x61\xbd\xfa\xb8\xbc\x5d\xae\x57\x5b\xac\x7f\xc3\x62\x75\x87\x3f\x96\xab\x8f\x09\x58\xfa\x92\x2d\xf8\xa1\xb6\x41\x87\xb1\x90\xc1\x5a\xce\x7a\x1f\xb7\xcc\x17\x44\x72\xd3\x12\x73\x35\x0b\x99\x4b\x01\x45\xba\x68\xa8\x60\x14\xe6\xc0\x56\x4b\x5d\xa0\x66\x5b\x49\x17\x1a\xed\x40\x3a\xeb\x90\x94\xac\xa4\x27\x1f\x4f\xaf\x04\xce\x46\xa3\x7f\x46\x40\x9a\xa2\x26\xeb\xf8\xb3\x33\x7a\x92\x91\xa7\xe9\xbc\x3d\x70\x31\x79\x17\x8e\x76\x08\x3e\xe8\x02\xe4\x40\xf8\xee\x8c\x46\x66\x44\x53\xb1\xf6\x49\xbc\x2e\xc2\x58\xf6\x8d\xd5\x6d\x99\x65\xd7\xa8\x60\x7a\xcc\xd6\xec\x61\xf6\xdf\x59\xf8\xd9\x08\xe7\xeb\xe6\x73\x38\x9f\xcd\x34\x79\x79\xe0\xc9\xf8\xf1\x7c\x3c\x4d\x46\x03\x66\x77\x54\xa9\x0b\x66\x2f\x11\xbb\x5b\x7c\xb9\x09\x07\x4c\xd5\x33\xb4\x48\xe3\x15\x59\x4b\xa7\x57\xfd\x4c\xbe\x44\xd2\xcd\x80\x05\x9c\xd4\x85\xe2\x16\x23\x22\xf7\x92\x71\x94\x4a\xc1\xf9\xf0\xdc\x73\x87\xcf\x59\xe4\xa0\x11\xaf\x68\xdf\x11\xa3\xbb\x72\x56\x1c\x0a\x1f\xc5\x07\x45\xcf\x89\x0f\xe7\x67\xf1\x15\x69\x99\xb3\xf3\xb1\x33\x07\x52\x0d\x27\x90\x3a\x63\xed\xa7\x73\x08\xa3\x0f\x6c\x7d\xd4\x71\xc9\x1e\xbb\x98\xbb\x6b\x41\xbc\x01\xf5\x26\xb1\x16\x26\x6b\x89\x8e\x6b\xcb\xde\x9f\xc6\x98\x54\xc1\x82\xd7\x4a\x6a\x9e\xe2\xf3\x76\xbd\x4a\x5a\xee\x4c\xa2\x6c\x11\x34\xbb\xe8\x91\xe2\x03\xab\x8e\x40\xfb\xda\xed\xda\x1f\x3b\xb8\x9a\x04\xbb\x20\xef\x65\xce\xef\xff\x3f\x9d\xcf\x31\x19\xc5\xb9\x34\x82\x14\x72\xbc\xbf\xb0\x60\x58\x1b\x96\x53\xc8\xcc\x9f\x08\x1f\x01\x57\xfe\xc4\xf9\x88\x69\x3f\xe4\x4b\xb4\xa4\x45\xb8\xf6\x85\xba\xb6\x5f\x76\x7c\xa8\xec\x99\xde\x0d\x43\xe7\xf6\xb1\x13\x54\xf3\x36\x5e\xb1\xe1\x82\x1f\x26\x6e\x3a\xc7\x9f\x8d\xf1\xdc\x4d\x5f\xc1\x0f\xa8\xd8\x93\x28\xc9\x92\xf0\x6c\x1d\x72\xd3\xe8\x2c\xec\xac\xe8\x66\x9a\xc6\x2f\x40\x3b\xa7\x61\x21\x51\x57\xe5\x4b\xea\xc6\xb0\x22\x2f\xda\x6d\x6c\xac\x2c\xa4\x26\x05\x25\x3d\x5b\x52\x6d\xfd\x19\x3b\x00\x5e\x71\x7a\xa2\xe4\x2a\x7e\x96\x63\xd9\x19\x75\xe0\x65\x45\x05\x4f\x64\x78\x3e\x71\x3b\x33\xe2\x9e\xc3\x32\x0b\x9b\xa9\x73\x36\xb7\xa6\x6a\xcb\xe3\xf1\xdc\x53\x01\xa9\xe3\x4c\x56\xc6\x0e\x56\x5a\x0c\xff\x92\xc9\x82\x9d\x4f\x90\x71\xcd\x3a\x0b\x00\x46\xf7\xdf\xbf\x4e\x8e\xa9\x2a\xd2\x19\xc2\xbc\x22\x57\x54\x44\x59\x43\x6e\x4f\x14\x0d\x43\x43\x31\x05\x3f\x7c\x09\xd6\x4d\xe2\xbf\x49\x47\x78\x3a\xc7\xa6\xdf\x62\xb6\x61\xc8\xbc\x33\x5c\x9e\x5b\x33\x1c\x9d\x19\x36\x7d\x98\x5c\xdc\xe3\xf1\x45\xe7\xd8\xc2\xc2\x84\x45\xdd\x02\xd4\xa8\x49\xdc\x53\xd1\x2d\x84\x49\x7d\xf2\xa5\xd1\xaf\xa5\x2b\xa7\xad\x80\x9e\xcf\x15\xfd\x3e\xf0\x84\xfc\xb6\xd9\x3b\xff\x48\xde\x8a\x04\x96\x6b\xf5\xc8\x7f\xb0\xdf\xc2\xb6\x0b\x31\x12\x52\x17\x03\x84\x38\x65\x56\xb4\xaf\x7b\x48\x98\x01\x9b\x98\x17\x35\xf4\x2d\x0c\x5f\x74\xa9\x85\x6a\x32\xc6\xff\xde\x26\x60\x2f\x1e\x37\x8b\xe5\x3c\x7c\x53\x0c\x5c\xb3\x8f\x83\xc8\x2e\x82\xfc\x98\x25\xfd\x8a\x8f\xbe\x3c\x6f\x49\x54\xf9\x9c\x25\x31\x10\x2c\xf9\x77\xf4\x5f\x00\x00\x00\xff\xff\x3a\x93\xab\x97\x36\x09\x00\x00") +var _libKubecfgLibsonnet = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x56\x5d\x6f\xdb\xb8\x12\x7d\xf7\xaf\x38\x30\xee\x83\x5d\x28\x76\x5b\x14\xf7\x02\xbe\x28\xb0\x6e\x9b\xa2\xee\xa6\x4e\xd7\x4e\xb6\xc8\x5b\xc6\xd4\x48\x62\x4b\x91\x5a\x92\x8a\x6d\x2c\xf6\xbf\x2f\x48\x49\x96\x3f\x52\xa0\x40\x60\x28\x9c\xe1\xe1\x99\x33\xa3\x43\x4d\xa7\x78\x6f\xaa\xbd\x95\x79\xe1\xf1\xfa\xe5\xab\xff\xe1\xae\x60\xfc\xa8\x37\x2c\xb2\x1c\x54\xfb\xc2\x58\x37\x98\x4e\x9b\x3f\x00\xb8\x91\x82\xb5\xe3\x14\xb5\x4e\xd9\xc2\x17\x8c\x79\x45\xa2\xe0\x2e\x92\xe0\x4f\xb6\x4e\x1a\x8d\xd7\x93\x97\x18\x85\x84\x61\x1b\x1a\x8e\xff\xdf\xa2\xec\x4d\x8d\x92\xf6\xd0\xc6\xa3\x76\x0c\x5f\x48\x87\x4c\x2a\x06\xef\x04\x57\x1e\x52\x43\x98\xb2\x52\x92\xb4\x60\x6c\xa5\x2f\xe2\x51\x2d\xd0\xa4\x85\x79\x68\x61\xcc\xc6\x93\xd4\x20\x08\x53\xed\x61\xb2\xe3\x5c\x90\xef\xd9\x03\x85\xf7\xd5\x6c\x3a\xdd\x6e\xb7\x13\x8a\xbc\x27\xc6\xe6\x53\xd5\xe4\xba\xe9\xcd\xe2\xfd\xf5\x72\x7d\x7d\xf5\x7a\xf2\xb2\xdf\x75\xaf\x15\x3b\x07\xcb\x7f\xd5\xd2\x72\x8a\xcd\x1e\x54\x55\x4a\x0a\xda\x28\x86\xa2\x2d\x8c\x05\xe5\x96\x39\x85\x37\x81\xfb\xd6\x4a\x2f\x75\x9e\xc0\x99\xcc\x6f\xc9\x72\x8b\x94\x4a\xe7\xad\xdc\xd4\xfe\x44\xc0\x8e\xa9\x74\x27\x09\x46\x83\x34\x86\xf3\x35\x16\xeb\x21\xde\xcd\xd7\x8b\x75\xd2\xe2\x7c\x5b\xdc\x7d\xba\xbd\xbf\xc3\xb7\xf9\x6a\x35\x5f\xde\x2d\xae\xd7\xb8\x5d\xe1\xfd\xed\xf2\xc3\xe2\x6e\x71\xbb\x5c\xe3\xf6\x23\xe6\xcb\x07\xfc\xbe\x58\x7e\x48\xc0\xd2\x17\x6c\xc1\xbb\xca\x86\x3a\x8c\x85\x0c\xd2\x72\xda\xe9\xb8\x66\x3e\x21\x92\x99\x86\x98\xab\x58\xc8\x4c\x0a\x28\xd2\x79\x4d\x39\x23\x37\x4f\x6c\xb5\xd4\x39\x2a\xb6\xa5\x74\xa1\xd1\x0e\xa4\xd3\x16\x49\xc9\x52\x7a\xf2\x71\xf5\xa2\xc0\xc9\x60\xf0\xf7\x00\x98\x4e\x51\x91\x75\xfc\xd9\x19\x3d\x4a\xc9\xd3\x78\xd6\x2c\xb8\x98\xfc\x18\x96\x1e\x11\x74\xd0\x39\xc8\x81\xf0\xdd\x19\x8d\xd4\x88\xba\x64\xed\x93\x78\x5c\x84\xb1\xec\x6b\xab\x9b\x6d\x96\x5d\xad\x82\xe8\x31\x5b\xb3\x87\xd9\x7c\x67\xe1\x27\x03\xf4\xc7\xcd\x66\x70\x3e\x9d\x68\xf2\xf2\x89\x47\xc3\xc3\xfa\x70\x9c\x0c\x8e\x98\x3d\x50\xa9\x4e\x98\xfd\x8c\xd8\xc3\xfc\xcb\x4d\x58\x60\x2a\x9f\xa1\x45\x1a\x2f\xc8\x5a\xda\xbf\xe8\x66\xf2\x67\x24\xdd\x04\x98\xc3\x49\x9d\x2b\x6e\x30\x22\x72\x57\x32\xb6\x52\x29\x38\x1f\x7e\x37\xdc\xe2\x73\x1a\x39\x68\xc4\x23\x9a\x77\xc4\xe8\x76\x3b\x2b\x0e\x1b\x0f\xc5\x87\x8a\x9e\x2b\x3e\xac\xf7\xc5\x97\xa4\x65\xc6\xce\xc7\xce\x3c\x91\xaa\x39\x81\xd4\x29\x6b\x3f\x9e\x41\x18\xfd\xc4\xd6\xc7\x3a\x4e\xd9\xe3\x31\xe6\x3e\x36\x20\xde\x80\x3a\x91\x58\x0b\x93\x36\x44\x87\x95\x65\xef\xf7\x43\x8c\xca\x20\xc1\x95\x92\x9a\xc7\xf8\xbc\xbe\x5d\x26\x0d\x77\x26\x51\x34\x08\x9a\x5d\xd4\x48\xf1\x13\xab\x96\x40\xf3\xda\x3d\x36\xff\x3c\xc2\x55\x24\xd8\x85\xf2\x7e\xce\xf9\xed\x9b\xf1\x6c\x86\xd1\x20\xce\xa5\x11\xa4\x90\xe1\xed\x89\x04\xc7\x7b\x83\x39\x85\xcc\xec\xac\xf0\x01\x70\xa1\x4f\x9c\x8f\x98\xf6\x4b\xba\x44\x49\x1a\x84\x4b\x5d\xa8\x6d\xfb\x69\xc7\x8f\x2b\x7b\xa6\x77\xc7\xa1\xbe\x7d\xec\x04\x55\xbc\x8e\x47\xac\x38\xe7\xdd\xc8\x8d\x67\xf8\xa3\x36\x9e\xdb\xe9\xcb\x79\x87\x92\x3d\x89\x82\x2c\x09\xcf\xd6\x21\x33\xb5\x4e\x83\x67\x45\x35\xa7\xd3\x78\x03\x34\x73\x1a\x0c\x89\xda\x5d\xbe\xa0\x76\x0c\x4b\xf2\xa2\x71\x63\x63\x65\x2e\x35\x29\x28\xe9\xd9\x92\x6a\xf6\xf7\xd8\x01\xf0\x82\xd3\x59\x25\x17\xf1\xbe\x1c\xcb\xce\xa8\x27\x5e\x94\x94\xf3\x48\x86\xdf\x33\xb5\x53\x23\x7e\x70\x30\xb3\xe0\x4c\xad\xb2\x99\x35\x65\xb3\x3d\x2e\xcf\x3c\xe5\x90\x3a\xce\x64\x69\xec\x91\xa5\xc5\xf0\x6f\xa9\xcc\xd9\xf9\x04\x29\x57\xac\xd3\x00\x60\x74\x77\xff\xb5\xe5\x98\xb2\x24\x9d\x22\xcc\x2b\x32\x45\x79\x2c\xeb\x98\xdb\x59\x45\xc7\xa1\xe3\x62\x72\xde\x7d\x09\xd2\x8d\xe2\x63\xd2\x12\x1e\xcf\xb0\xea\x5c\xcc\xd6\x0c\x99\xb5\x82\xcb\xbe\x35\xc7\xa3\x33\xc1\xaa\x0b\x93\x8b\x3e\x1e\x5f\x74\x8e\x2d\xcc\x4d\x30\xea\x06\xa0\x42\x45\xe2\x07\xe5\xad\x21\x8c\xaa\xbd\x2f\x8c\xbe\x92\xae\x18\x37\x05\x74\x7c\x2e\xe8\x77\x81\x33\xf2\xeb\x7a\xe3\xfc\x81\xbc\x15\x09\x2c\x57\xea\xc0\xff\xc8\xdf\x82\xdb\x85\x18\x09\xa9\xf3\x23\x84\x38\x65\x56\x34\xaf\x7b\x48\x98\x00\xab\x98\x17\x6b\xe8\x5a\x18\x6e\x74\xa9\x85\xaa\x53\xc6\x7f\x5e\x25\x60\x2f\x0e\xce\x62\x39\x0b\x77\x8a\x81\xab\x37\x71\x10\xd9\x45\x90\x5f\x93\xa4\xb3\xf8\xa8\xcb\xf3\x92\xc4\x2a\x9f\x93\x24\x06\x7a\x49\x72\xd6\x6c\xc9\xf3\x57\x72\x6e\x6b\x6c\x3a\x52\xac\x73\x5f\x24\xd0\x75\xf9\x41\xe6\xd2\xbb\xf8\xb8\xde\x97\x1b\xa3\xc2\xb3\xb9\xaf\x2a\xb6\x09\x48\x29\xb3\x5d\x71\xc5\xe4\x13\x51\x3b\x6f\xba\x9c\xf1\xac\x41\x6e\xe5\xa4\xc3\x11\x29\xaa\xf6\x90\xfe\x43\x28\x63\xf2\xb5\x65\xd7\xcd\x73\x63\x8f\x21\x52\x91\xa5\x92\xbb\xd7\xef\x9c\xe6\x59\x65\xe7\xe1\x23\x2f\xd9\xb1\xf8\x6a\x4d\x6e\xa9\x1c\x69\x2a\x39\x01\xd9\x3c\x5a\x93\x4b\x90\x91\x54\xb7\xfa\xda\x5a\x63\xc7\x33\x5c\xef\x58\xd4\x9e\x83\x57\x54\xcd\x8e\x70\x15\x76\xb7\x60\xdb\xba\x7e\x38\x0e\x26\xd8\x58\x0a\xb9\xf8\xb5\xe4\x59\xc7\xb6\xfa\xd4\xd4\x7e\x82\xf6\x68\x94\xb5\xf3\x68\x20\x36\x1c\xfa\xf9\x75\x7e\xf7\x29\xfa\x4a\xcf\xef\xdc\x51\xfa\x48\x5f\x8e\xf6\x9f\xc8\x15\x1f\xad\x29\x0f\x0d\xeb\x44\x3d\x9d\xe0\xe5\xdd\xe8\xe6\xcb\x18\x05\xb9\x22\x7e\x06\xd1\x41\xfc\x70\xea\x25\xcc\xd9\xe1\x97\x09\x47\x92\x46\xd3\x7f\x47\x8e\xff\xfb\xe6\xde\xaa\x91\xe7\x9d\xef\x0f\xdf\xc4\xf5\xab\xda\xaa\xab\xee\x76\xe8\x2c\x2d\x7c\x8d\xf1\x2e\xde\x09\x67\x18\xe7\xa5\x9f\x46\xc3\xd1\xff\x0c\xfe\x0d\x00\x00\xff\xff\x2e\x7b\xd2\x11\xe4\x0b\x00\x00") func libKubecfgLibsonnetBytes() ([]byte, error) { return bindataRead( diff --git a/utils/nativefuncs.go b/utils/nativefuncs.go index d838d171..7ac6376e 100644 --- a/utils/nativefuncs.go +++ b/utils/nativefuncs.go @@ -17,11 +17,18 @@ package utils import ( "bytes" + "encoding/base64" "encoding/json" + "errors" "io" "regexp" + "os/exec" "strings" + "github.com/DaKnOb/ntlm" + "github.com/mattn/go-shellwords" + "github.com/sethvargo/go-password/password" + goyaml "github.com/ghodss/yaml" jsonnet "github.com/google/go-jsonnet" @@ -42,6 +49,51 @@ func resolveImage(resolver Resolver, image string) (string, error) { return n.String(), nil } +func generatePassword(length int, numDigits int, numSymbols int, noUpper bool, allowRepeat bool, customSymbols string) (string, error) { + // exclude some of the default symbols as they cause problems when using them + // as arguments on the command line (as part of JSON being passed) + input := &password.GeneratorInput { + LowerLetters: "", + UpperLetters: "", + Digits: "", + Symbols: customSymbols, + } + g, err := password.NewGenerator(input) + + if err != nil { + return "", err + } + return g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat) +} + +func execProgram(name string, argumentsString string, failOnError bool) (string, error) { + arg, err := shellwords.Parse(argumentsString) + if err != nil { + if failOnError { + return "", err + } + return "", nil + } + + out, err := exec.Command(name, arg...).CombinedOutput() + if err != nil { + if failOnError { + return "", errors.New(string(out)) + } + return "", nil + } + + return string(out), nil +} + +func ntHashFromPassword(password string) (string) { + return string(ntlm.FromASCIIStringToHex(password)) +} + +func encodeBase64Url(text string) (string) { + return base64.URLEncoding.EncodeToString([]byte(text)) +} + // RegisterNativeFuncs adds kubecfg's native jsonnet functions to provided VM func RegisterNativeFuncs(vm *jsonnet.VM, resolver Resolver) { // TODO(mkm): go-jsonnet 0.12.x now contains native std.parseJson; deprecate and remove this one. @@ -140,4 +192,37 @@ func RegisterNativeFuncs(vm *jsonnet.VM, resolver Resolver) { return r.ReplaceAllString(src, repl), nil }, }) + + vm.NativeFunction(&jsonnet.NativeFunction{ + Name: "generatePassword", + Params: []jsonnetAst.Identifier{"length", "numDigits", "numSymbols", "noUpper", "allowRepeat", "customSymbols"}, + Func: func(args []interface{}) (res interface{}, err error) { + return generatePassword(int(args[0].(float64)), int(args[1].(float64)), int(args[2].(float64)), args[3].(bool), args[4].(bool), args[5].(string) ) + }, + }) + + vm.NativeFunction(&jsonnet.NativeFunction{ + Name: "execProgram", + Params: []jsonnetAst.Identifier{"name", "arguments", "failOnError"}, + Func: func(args []interface{}) (res interface{}, err error) { + return execProgram(args[0].(string), args[1].(string), args[2].(bool)) + }, + }) + + vm.NativeFunction(&jsonnet.NativeFunction{ + Name: "ntHashFromPassword", + Params: []jsonnetAst.Identifier{"password"}, + Func: func(args []interface{}) (res interface{}, err error) { + return ntHashFromPassword(args[0].(string)), nil + }, + }) + + vm.NativeFunction(&jsonnet.NativeFunction{ + Name: "encodeBase64Url", + Params: []jsonnetAst.Identifier{"text"}, + Func: func(args []interface{}) (res interface{}, err error) { + return encodeBase64Url(args[0].(string)), nil + }, + }) + } diff --git a/utils/nativefuncs_test.go b/utils/nativefuncs_test.go index cb98f10d..f3500cfb 100644 --- a/utils/nativefuncs_test.go +++ b/utils/nativefuncs_test.go @@ -17,7 +17,7 @@ package utils import ( "testing" - + jsonnet "github.com/google/go-jsonnet" ) @@ -102,3 +102,45 @@ func TestRegexSubst(t *testing.T) { x, err = vm.EvaluateSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "${1}W")`) check(t, err, x, "\"-W-xxW-\"\n") } + +func TestGeneratePassword(t *testing.T) { + vm := jsonnet.MakeVM() + RegisterNativeFuncs(vm, NewIdentityResolver()) + + _, err := vm.EvaluateSnippet("test", `std.native("generatePassword")(16, 4, 5, true, true, "")`) + + if err != nil { + t.Errorf("Got error: %q", err.Error()) + } +} + +func TestExecProgram(t *testing.T) { + vm := jsonnet.MakeVM() + RegisterNativeFuncs(vm, NewIdentityResolver()) + + _, err := vm.EvaluateSnippet("test", `std.native("execProgram")("ls", "-a -l", true)`) + + if err != nil { + t.Errorf("Got error: %q", err.Error()) + } +} + +func TestNtHashFromPassword(t *testing.T) { + vm := jsonnet.MakeVM() + RegisterNativeFuncs(vm, NewIdentityResolver()) + + r, err := vm.EvaluateSnippet("test", `std.native("ntHashFromPassword")("admin")`) + + check(t, err , r, "\"209c6174da490caeb422f3fa5a7ae634\"\n") +} + +func TestEncodeBase64Url(t *testing.T) { + vm := jsonnet.MakeVM() + RegisterNativeFuncs(vm, NewIdentityResolver()) + + r, err := vm.EvaluateSnippet("test", `std.native("encodeBase64Url")("This is a test")`) + + // echo -n "This is a test" | base64 + // result: VGhpcyBpcyBhIHRlc3Q= + check(t, err , r, "\"VGhpcyBpcyBhIHRlc3Q=\"\n") +} diff --git a/vendor/github.com/DaKnOb/ntlm/.gitignore b/vendor/github.com/DaKnOb/ntlm/.gitignore new file mode 100644 index 00000000..a1338d68 --- /dev/null +++ b/vendor/github.com/DaKnOb/ntlm/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ diff --git a/vendor/github.com/DaKnOb/ntlm/LICENSE b/vendor/github.com/DaKnOb/ntlm/LICENSE new file mode 100644 index 00000000..412f9a5e --- /dev/null +++ b/vendor/github.com/DaKnOb/ntlm/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, Antonios A. Chariton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/DaKnOb/ntlm/ntlm.go b/vendor/github.com/DaKnOb/ntlm/ntlm.go new file mode 100644 index 00000000..30cfc444 --- /dev/null +++ b/vendor/github.com/DaKnOb/ntlm/ntlm.go @@ -0,0 +1,37 @@ +package ntlm + +import ( + "encoding/hex" + + "golang.org/x/crypto/md4" +) + +/* +FromASCIIString calculates the NTLM hash of an ASCII string (in) +*/ +func FromASCIIString(in string) []byte { + /* Prepare a byte array to return */ + var u16 []byte + + /* Add all bytes, as well as the 0x00 of UTF-16 */ + for _, b := range []byte(in) { + u16 = append(u16, b) + u16 = append(u16, 0x00) + } + + /* Hash the byte array with MD4 */ + mdfour := md4.New() + mdfour.Write(u16) + + /* Return the output */ + return mdfour.Sum(nil) +} + +/* +FromASCIIStringToHex calculates the NTLM hash of an ASCII string (in) +and returns it as a hexademical hash in a string, e.g. 00feabcd +*/ +func FromASCIIStringToHex(in string) string { + b := FromASCIIString(in) + return hex.EncodeToString(b) +} diff --git a/vendor/github.com/mattn/go-shellwords/.travis.yml b/vendor/github.com/mattn/go-shellwords/.travis.yml new file mode 100644 index 00000000..6294d337 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/.travis.yml @@ -0,0 +1,14 @@ +language: go +sudo: false +go: + - tip + +before_install: + - go get -t -v ./... + +script: + - ./go.test.sh + +after_success: + - bash <(curl -s https://codecov.io/bash) + diff --git a/vendor/github.com/mattn/go-shellwords/LICENSE b/vendor/github.com/mattn/go-shellwords/LICENSE new file mode 100644 index 00000000..740fa931 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Yasuhiro Matsumoto + +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. diff --git a/vendor/github.com/mattn/go-shellwords/README.md b/vendor/github.com/mattn/go-shellwords/README.md new file mode 100644 index 00000000..9e1e6504 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/README.md @@ -0,0 +1,47 @@ +# go-shellwords + +[![codecov](https://codecov.io/gh/mattn/go-shellwords/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-shellwords) +[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords) + +Parse line as shell words. + +## Usage + +```go +args, err := shellwords.Parse("./foo --bar=baz") +// args should be ["./foo", "--bar=baz"] +``` + +```go +os.Setenv("FOO", "bar") +p := shellwords.NewParser() +p.ParseEnv = true +args, err := p.Parse("./foo $FOO") +// args should be ["./foo", "bar"] +``` + +```go +p := shellwords.NewParser() +p.ParseBacktick = true +args, err := p.Parse("./foo `echo $SHELL`") +// args should be ["./foo", "/bin/bash"] +``` + +```go +shellwords.ParseBacktick = true +p := shellwords.NewParser() +args, err := p.Parse("./foo `echo $SHELL`") +// args should be ["./foo", "/bin/bash"] +``` + +# Thanks + +This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine). + +# License + +under the MIT License: http://mattn.mit-license.org/2017 + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-shellwords/go.mod b/vendor/github.com/mattn/go-shellwords/go.mod new file mode 100644 index 00000000..8d96dbd5 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/go.mod @@ -0,0 +1 @@ +module github.com/mattn/go-shellwords diff --git a/vendor/github.com/mattn/go-shellwords/go.test.sh b/vendor/github.com/mattn/go-shellwords/go.test.sh new file mode 100644 index 00000000..a7deaca9 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/go.test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e +echo "" > coverage.txt + +for d in $(go list ./... | grep -v vendor); do + go test -coverprofile=profile.out -covermode=atomic "$d" + if [ -f profile.out ]; then + cat profile.out >> coverage.txt + rm profile.out + fi +done diff --git a/vendor/github.com/mattn/go-shellwords/shellwords.go b/vendor/github.com/mattn/go-shellwords/shellwords.go new file mode 100644 index 00000000..2dca7f13 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/shellwords.go @@ -0,0 +1,193 @@ +package shellwords + +import ( + "errors" + "os" + "regexp" + "strings" +) + +var ( + ParseEnv bool = false + ParseBacktick bool = false +) + +var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`) + +func isSpace(r rune) bool { + switch r { + case ' ', '\t', '\r', '\n': + return true + } + return false +} + +func replaceEnv(getenv func(string) string, s string) string { + if getenv == nil { + getenv = os.Getenv + } + + return envRe.ReplaceAllStringFunc(s, func(s string) string { + s = s[1:] + if s[0] == '{' { + s = s[1 : len(s)-1] + } + return getenv(s) + }) +} + +type Parser struct { + ParseEnv bool + ParseBacktick bool + Position int + Dir string + + // If ParseEnv is true, use this for getenv. + // If nil, use os.Getenv. + Getenv func(string) string +} + +func NewParser() *Parser { + return &Parser{ + ParseEnv: ParseEnv, + ParseBacktick: ParseBacktick, + Position: 0, + Dir: "", + } +} + +func (p *Parser) Parse(line string) ([]string, error) { + args := []string{} + buf := "" + var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool + backtick := "" + + pos := -1 + got := false + +loop: + for i, r := range line { + if escaped { + buf += string(r) + escaped = false + continue + } + + if r == '\\' { + if singleQuoted { + buf += string(r) + } else { + escaped = true + } + continue + } + + if isSpace(r) { + if singleQuoted || doubleQuoted || backQuote || dollarQuote { + buf += string(r) + backtick += string(r) + } else if got { + if p.ParseEnv { + buf = replaceEnv(p.Getenv, buf) + } + args = append(args, buf) + buf = "" + got = false + } + continue + } + + switch r { + case '`': + if !singleQuoted && !doubleQuoted && !dollarQuote { + if p.ParseBacktick { + if backQuote { + out, err := shellRun(backtick, p.Dir) + if err != nil { + return nil, err + } + buf = buf[:len(buf)-len(backtick)] + out + } + backtick = "" + backQuote = !backQuote + continue + } + backtick = "" + backQuote = !backQuote + } + case ')': + if !singleQuoted && !doubleQuoted && !backQuote { + if p.ParseBacktick { + if dollarQuote { + out, err := shellRun(backtick, p.Dir) + if err != nil { + return nil, err + } + buf = buf[:len(buf)-len(backtick)-2] + out + } + backtick = "" + dollarQuote = !dollarQuote + continue + } + backtick = "" + dollarQuote = !dollarQuote + } + case '(': + if !singleQuoted && !doubleQuoted && !backQuote { + if !dollarQuote && strings.HasSuffix(buf, "$") { + dollarQuote = true + buf += "(" + continue + } else { + return nil, errors.New("invalid command line string") + } + } + case '"': + if !singleQuoted && !dollarQuote { + doubleQuoted = !doubleQuoted + continue + } + case '\'': + if !doubleQuoted && !dollarQuote { + singleQuoted = !singleQuoted + continue + } + case ';', '&', '|', '<', '>': + if !(escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote) { + if r == '>' && len(buf) > 0 { + if c := buf[0]; '0' <= c && c <= '9' { + i -= 1 + got = false + } + } + pos = i + break loop + } + } + + got = true + buf += string(r) + if backQuote || dollarQuote { + backtick += string(r) + } + } + + if got { + if p.ParseEnv { + buf = replaceEnv(p.Getenv, buf) + } + args = append(args, buf) + } + + if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote { + return nil, errors.New("invalid command line string") + } + + p.Position = pos + + return args, nil +} + +func Parse(line string) ([]string, error) { + return NewParser().Parse(line) +} diff --git a/vendor/github.com/mattn/go-shellwords/util_go15.go b/vendor/github.com/mattn/go-shellwords/util_go15.go new file mode 100644 index 00000000..ddcbf229 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/util_go15.go @@ -0,0 +1,29 @@ +// +build !go1.6 + +package shellwords + +import ( + "os" + "os/exec" + "runtime" + "strings" +) + +func shellRun(line, dir string) (string, error) { + var b []byte + var err error + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command(os.Getenv("COMSPEC"), "/c", line) + } else { + cmd = exec.Command(os.Getenv("SHELL"), "-c", line) + } + if dir != "" { + cmd.Dir = dir + } + b, err = cmd.Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(b)), nil +} diff --git a/vendor/github.com/mattn/go-shellwords/util_posix.go b/vendor/github.com/mattn/go-shellwords/util_posix.go new file mode 100644 index 00000000..3aef2c4d --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/util_posix.go @@ -0,0 +1,26 @@ +// +build !windows,go1.6 + +package shellwords + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +func shellRun(line, dir string) (string, error) { + shell := os.Getenv("SHELL") + cmd := exec.Command(shell, "-c", line) + if dir != "" { + cmd.Dir = dir + } + b, err := cmd.Output() + if err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + b = eerr.Stderr + } + return "", errors.New(err.Error() + ":" + string(b)) + } + return strings.TrimSpace(string(b)), nil +} diff --git a/vendor/github.com/mattn/go-shellwords/util_windows.go b/vendor/github.com/mattn/go-shellwords/util_windows.go new file mode 100644 index 00000000..cda68509 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/util_windows.go @@ -0,0 +1,26 @@ +// +build windows,go1.6 + +package shellwords + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +func shellRun(line, dir string) (string, error) { + shell := os.Getenv("COMSPEC") + cmd := exec.Command(shell, "/c", line) + if dir != "" { + cmd.Dir = dir + } + b, err := cmd.Output() + if err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + b = eerr.Stderr + } + return "", errors.New(err.Error() + ":" + string(b)) + } + return strings.TrimSpace(string(b)), nil +} diff --git a/vendor/github.com/sethvargo/go-password/LICENSE b/vendor/github.com/sethvargo/go-password/LICENSE new file mode 100644 index 00000000..c62ec3de --- /dev/null +++ b/vendor/github.com/sethvargo/go-password/LICENSE @@ -0,0 +1,20 @@ +Copyright 2017 Seth Vargo + +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. diff --git a/vendor/github.com/sethvargo/go-password/password/generate.go b/vendor/github.com/sethvargo/go-password/password/generate.go new file mode 100644 index 00000000..176e00a2 --- /dev/null +++ b/vendor/github.com/sethvargo/go-password/password/generate.go @@ -0,0 +1,242 @@ +// Package password provides a library for generating high-entropy random +// password strings via the crypto/rand package. +// +// res, err := Generate(64, 10, 10, false, false) +// if err != nil { +// log.Fatal(err) +// } +// log.Printf(res) +// +// Most functions are safe for concurrent use. +package password + +import ( + "crypto/rand" + "errors" + "math/big" + "strings" +) + +const ( + // LowerLetters is the list of lowercase letters. + LowerLetters = "abcdefghijklmnopqrstuvwxyz" + + // UpperLetters is the list of uppercase letters. + UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + // Digits is the list of permitted digits. + Digits = "0123456789" + + // Symbols is the list of symbols. + Symbols = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./" +) + +var ( + // ErrExceedsTotalLength is the error returned with the number of digits and + // symbols is greater than the total length. + ErrExceedsTotalLength = errors.New("number of digits and symbols must be less than total length") + + // ErrLettersExceedsAvailable is the error returned with the number of letters + // exceeds the number of available letters and repeats are not allowed. + ErrLettersExceedsAvailable = errors.New("number of letters exceeds available letters and repeats are not allowed") + + // ErrDigitsExceedsAvailable is the error returned with the number of digits + // exceeds the number of available digits and repeats are not allowed. + ErrDigitsExceedsAvailable = errors.New("number of digits exceeds available digits and repeats are not allowed") + + // ErrSymbolsExceedsAvailable is the error returned with the number of symbols + // exceeds the number of available symbols and repeats are not allowed. + ErrSymbolsExceedsAvailable = errors.New("number of symbols exceeds available symbols and repeats are not allowed") +) + +// Generator is the stateful generator which can be used to customize the list +// of letters, digits, and/or symbols. +type Generator struct { + lowerLetters string + upperLetters string + digits string + symbols string +} + +// GeneratorInput is used as input to the NewGenerator function. +type GeneratorInput struct { + LowerLetters string + UpperLetters string + Digits string + Symbols string +} + +// NewGenerator creates a new Generator from the specified configuration. If no +// input is given, all the default values are used. This function is safe for +// concurrent use. +func NewGenerator(i *GeneratorInput) (*Generator, error) { + if i == nil { + i = new(GeneratorInput) + } + + g := &Generator{ + lowerLetters: i.LowerLetters, + upperLetters: i.UpperLetters, + digits: i.Digits, + symbols: i.Symbols, + } + + if g.lowerLetters == "" { + g.lowerLetters = LowerLetters + } + + if g.upperLetters == "" { + g.upperLetters = UpperLetters + } + + if g.digits == "" { + g.digits = Digits + } + + if g.symbols == "" { + g.symbols = Symbols + } + + return g, nil +} + +// Generate generates a password with the given requirements. length is the +// total number of characters in the password. numDigits is the number of digits +// to include in the result. numSymbols is the number of symbols to include in +// the result. noUpper excludes uppercase letters from the results. allowRepeat +// allows characters to repeat. +// +// The algorithm is fast, but it's not designed to be performant; it favors +// entropy over speed. This function is safe for concurrent use. +func (g *Generator) Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) { + letters := g.lowerLetters + if !noUpper { + letters += g.upperLetters + } + + chars := length - numDigits - numSymbols + if chars < 0 { + return "", ErrExceedsTotalLength + } + + if !allowRepeat && chars > len(letters) { + return "", ErrLettersExceedsAvailable + } + + if !allowRepeat && numDigits > len(g.digits) { + return "", ErrDigitsExceedsAvailable + } + + if !allowRepeat && numSymbols > len(g.symbols) { + return "", ErrSymbolsExceedsAvailable + } + + var result string + + // Characters + for i := 0; i < chars; i++ { + ch, err := randomElement(letters) + if err != nil { + return "", err + } + + if !allowRepeat && strings.Contains(result, ch) { + i-- + continue + } + + result, err = randomInsert(result, ch) + if err != nil { + return "", err + } + } + + // Digits + for i := 0; i < numDigits; i++ { + d, err := randomElement(g.digits) + if err != nil { + return "", err + } + + if !allowRepeat && strings.Contains(result, d) { + i-- + continue + } + + result, err = randomInsert(result, d) + if err != nil { + return "", err + } + } + + // Symbols + for i := 0; i < numSymbols; i++ { + sym, err := randomElement(g.symbols) + if err != nil { + return "", err + } + + if !allowRepeat && strings.Contains(result, sym) { + i-- + continue + } + + result, err = randomInsert(result, sym) + if err != nil { + return "", err + } + } + + return result, nil +} + +// MustGenerate is the same as Generate, but panics on error. +func (g *Generator) MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string { + res, err := g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat) + if err != nil { + panic(err) + } + return res +} + +// See Generator.Generate for usage. +func Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) { + gen, err := NewGenerator(nil) + if err != nil { + return "", err + } + + return gen.Generate(length, numDigits, numSymbols, noUpper, allowRepeat) +} + +// See Generator.MustGenerate for usage. +func MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string { + res, err := Generate(length, numDigits, numSymbols, noUpper, allowRepeat) + if err != nil { + panic(err) + } + return res +} + +// randomInsert randomly inserts the given value into the given string. +func randomInsert(s, val string) (string, error) { + if s == "" { + return val, nil + } + + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s)+1))) + if err != nil { + return "", err + } + i := n.Int64() + return s[0:i] + val + s[i:len(s)], nil +} + +// randomElement extracts a random element from the given string. +func randomElement(s string) (string, error) { + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s)))) + if err != nil { + return "", err + } + return string(s[n.Int64()]), nil +} diff --git a/vendor/golang.org/x/crypto/md4/md4.go b/vendor/golang.org/x/crypto/md4/md4.go new file mode 100644 index 00000000..59d34806 --- /dev/null +++ b/vendor/golang.org/x/crypto/md4/md4.go @@ -0,0 +1,122 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package md4 implements the MD4 hash algorithm as defined in RFC 1320. +// +// Deprecated: MD4 is cryptographically broken and should should only be used +// where compatibility with legacy systems, not security, is the goal. Instead, +// use a secure hash like SHA-256 (from crypto/sha256). +package md4 // import "golang.org/x/crypto/md4" + +import ( + "crypto" + "hash" +) + +func init() { + crypto.RegisterHash(crypto.MD4, New) +} + +// The size of an MD4 checksum in bytes. +const Size = 16 + +// The blocksize of MD4 in bytes. +const BlockSize = 64 + +const ( + _Chunk = 64 + _Init0 = 0x67452301 + _Init1 = 0xEFCDAB89 + _Init2 = 0x98BADCFE + _Init3 = 0x10325476 +) + +// digest represents the partial evaluation of a checksum. +type digest struct { + s [4]uint32 + x [_Chunk]byte + nx int + len uint64 +} + +func (d *digest) Reset() { + d.s[0] = _Init0 + d.s[1] = _Init1 + d.s[2] = _Init2 + d.s[3] = _Init3 + d.nx = 0 + d.len = 0 +} + +// New returns a new hash.Hash computing the MD4 checksum. +func New() hash.Hash { + d := new(digest) + d.Reset() + return d +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.len += uint64(nn) + if d.nx > 0 { + n := len(p) + if n > _Chunk-d.nx { + n = _Chunk - d.nx + } + for i := 0; i < n; i++ { + d.x[d.nx+i] = p[i] + } + d.nx += n + if d.nx == _Chunk { + _Block(d, d.x[0:]) + d.nx = 0 + } + p = p[n:] + } + n := _Block(d, p) + p = p[n:] + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d0 *digest) Sum(in []byte) []byte { + // Make a copy of d0, so that caller can keep writing and summing. + d := new(digest) + *d = *d0 + + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + len := d.len + var tmp [64]byte + tmp[0] = 0x80 + if len%64 < 56 { + d.Write(tmp[0 : 56-len%64]) + } else { + d.Write(tmp[0 : 64+56-len%64]) + } + + // Length in bits. + len <<= 3 + for i := uint(0); i < 8; i++ { + tmp[i] = byte(len >> (8 * i)) + } + d.Write(tmp[0:8]) + + if d.nx != 0 { + panic("d.nx != 0") + } + + for _, s := range d.s { + in = append(in, byte(s>>0)) + in = append(in, byte(s>>8)) + in = append(in, byte(s>>16)) + in = append(in, byte(s>>24)) + } + return in +} diff --git a/vendor/golang.org/x/crypto/md4/md4block.go b/vendor/golang.org/x/crypto/md4/md4block.go new file mode 100644 index 00000000..3fed475f --- /dev/null +++ b/vendor/golang.org/x/crypto/md4/md4block.go @@ -0,0 +1,89 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// MD4 block step. +// In its own file so that a faster assembly or C version +// can be substituted easily. + +package md4 + +var shift1 = []uint{3, 7, 11, 19} +var shift2 = []uint{3, 5, 9, 13} +var shift3 = []uint{3, 9, 11, 15} + +var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15} +var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15} + +func _Block(dig *digest, p []byte) int { + a := dig.s[0] + b := dig.s[1] + c := dig.s[2] + d := dig.s[3] + n := 0 + var X [16]uint32 + for len(p) >= _Chunk { + aa, bb, cc, dd := a, b, c, d + + j := 0 + for i := 0; i < 16; i++ { + X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 + j += 4 + } + + // If this needs to be made faster in the future, + // the usual trick is to unroll each of these + // loops by a factor of 4; that lets you replace + // the shift[] lookups with constants and, + // with suitable variable renaming in each + // unrolled body, delete the a, b, c, d = d, a, b, c + // (or you can let the optimizer do the renaming). + // + // The index variables are uint so that % by a power + // of two can be optimized easily by a compiler. + + // Round 1. + for i := uint(0); i < 16; i++ { + x := i + s := shift1[i%4] + f := ((c ^ d) & b) ^ d + a += f + X[x] + a = a<>(32-s) + a, b, c, d = d, a, b, c + } + + // Round 2. + for i := uint(0); i < 16; i++ { + x := xIndex2[i] + s := shift2[i%4] + g := (b & c) | (b & d) | (c & d) + a += g + X[x] + 0x5a827999 + a = a<>(32-s) + a, b, c, d = d, a, b, c + } + + // Round 3. + for i := uint(0); i < 16; i++ { + x := xIndex3[i] + s := shift3[i%4] + h := b ^ c ^ d + a += h + X[x] + 0x6ed9eba1 + a = a<>(32-s) + a, b, c, d = d, a, b, c + } + + a += aa + b += bb + c += cc + d += dd + + p = p[_Chunk:] + n += _Chunk + } + + dig.s[0] = a + dig.s[1] = b + dig.s[2] = c + dig.s[3] = d + return n +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 77456cde..88dd51d1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -11,6 +11,8 @@ github.com/Azure/go-autorest/autorest/date github.com/Azure/go-autorest/logger # github.com/Azure/go-autorest/tracing v0.5.0 github.com/Azure/go-autorest/tracing +# github.com/DaKnOb/ntlm v0.0.0-20180331151128-c6369eea43f9 +github.com/DaKnOb/ntlm # github.com/Microsoft/go-winio v0.4.14 github.com/Microsoft/go-winio github.com/Microsoft/go-winio/pkg/guid @@ -136,6 +138,8 @@ github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter # github.com/mattn/go-isatty v0.0.11 github.com/mattn/go-isatty +# github.com/mattn/go-shellwords v1.0.6 +github.com/mattn/go-shellwords # github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd github.com/modern-go/concurrent # github.com/modern-go/reflect2 v1.0.1 @@ -187,6 +191,8 @@ github.com/pkg/errors github.com/pmezard/go-difflib/difflib # github.com/sergi/go-diff v1.1.0 github.com/sergi/go-diff/diffmatchpatch +# github.com/sethvargo/go-password v0.1.2 +github.com/sethvargo/go-password/password # github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus # github.com/spf13/cobra v1.0.0 @@ -197,6 +203,7 @@ github.com/spf13/pflag github.com/stretchr/testify/assert github.com/stretchr/testify/require # golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 +golang.org/x/crypto/md4 golang.org/x/crypto/ssh/terminal # golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/net/context