Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the C# bits in Go #87

Merged
merged 5 commits into from Mar 14, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
237 changes: 237 additions & 0 deletions GCEAgent/accounts.go
@@ -0,0 +1,237 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.

package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"math/big"
"os/user"
"reflect"
"strings"
"time"

"../logger"

"github.com/dchest/uniuri"
)

var (
regName = "PublicKeys"
pwChars = []byte(`abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789()~!@#$%^&*-+=|\{}[]:;<>,.?/`)
pwLgth = 15
accountDisabled = false
)

type windowsKeyJSON struct {
Email string
ExpireOn string
Exponent string
Modulus string
UserName string
}

var badExpire []string

func (k windowsKeyJSON) expired() bool {
t, err := time.Parse(time.RFC3339, k.ExpireOn)
if err != nil {
if !containsString(k.ExpireOn, badExpire) {
logger.Errorln("Error parsing time:", err)
badExpire = append(badExpire, k.ExpireOn)
}
return true
}
return t.Before(time.Now())
}

func (k windowsKeyJSON) createOrResetPwd() (*credsJSON, error) {
pwd := uniuri.NewLenChars(pwLgth, pwChars)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little concerned about using an external library for this - let's discuss tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually not sure why I settled on this library, it's not exactly unique in its generation

if _, err := user.Lookup(k.UserName); err == nil {
logger.Infoln("Resetting password for user", k.UserName)
if err := resetPwd(k.UserName, pwd); err != nil {
return nil, fmt.Errorf("error running resetPwd: %v", err)
}
} else {
logger.Infoln("Creating user", k.UserName)
if err := createAdminUser(k.UserName, pwd); err != nil {
return nil, fmt.Errorf("error running createUser: %v", err)
}
}

return createcredsJSON(k, pwd)
}

func createcredsJSON(k windowsKeyJSON, pwd string) (*credsJSON, error) {
mod, err := base64.StdEncoding.DecodeString(k.Modulus)
if err != nil {
return nil, fmt.Errorf("error decoding modulus: %v", err)
}
exp, err := base64.StdEncoding.DecodeString(k.Exponent)
if err != nil {
return nil, fmt.Errorf("error decoding exponent: %v", err)
}

key := &rsa.PublicKey{
N: new(big.Int).SetBytes(mod),
E: int(new(big.Int).SetBytes(exp).Int64()),
}

encPwd, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, key, []byte(pwd), nil)
if err != nil {
return nil, fmt.Errorf("error encrypting password: %v", err)
}

return &credsJSON{
PasswordFound: true,
Exponent: k.Exponent,
Modulus: k.Modulus,
UserName: k.UserName,
EncryptedPassword: base64.StdEncoding.EncodeToString(encPwd),
}, nil
}

type accounts struct {
newMetadata, oldMetadata *metadataJSON
}

func (a *accounts) diff() bool {
return !reflect.DeepEqual(a.newMetadata.Instance.Attributes.WindowsKeys, a.oldMetadata.Instance.Attributes.WindowsKeys)
}

func (a *accounts) disabled() bool {
d := a.newMetadata.Instance.Attributes.DisableAccountManager
if d != accountDisabled {
accountDisabled = d
logStatus("account", d)
}

return d
}

type credsJSON struct {
ErrorMessage string `JSON:"errorMessage,omitempty"`
EncryptedPassword string `JSON:"encryptedPassword,omitempty"`
UserName string `JSON:"userName,omitempty"`
PasswordFound bool `JSON:"passwordFound,omitempty"`
Exponent string `JSON:"exponent,omitempty"`
Modulus string `JSON:"modulus,omitempty"`
}

func printCreds(creds *credsJSON) error {
data, err := json.Marshal(creds)
if err != nil {
return err
}
return writeSerial("COM4", append(data, []byte("\n")...))
}

var badReg []string

func compareAccounts(newKeys []windowsKeyJSON, oldStrKeys []string) []windowsKeyJSON {
if len(newKeys) == 0 {
return nil
}
if len(oldStrKeys) == 0 {
return newKeys
}

var oldKeys []windowsKeyJSON
for _, s := range oldStrKeys {
var key windowsKeyJSON
if err := json.Unmarshal([]byte(s), &key); err != nil {
if !containsString(s, badReg) {
logger.Error(err)
badReg = append(badReg, s)
}
continue
}
oldKeys = append(oldKeys, key)
}

var toAdd []windowsKeyJSON
for _, key := range newKeys {
if func(key windowsKeyJSON, oldKeys []windowsKeyJSON) bool {
for _, oldKey := range oldKeys {
if reflect.DeepEqual(oldKey, key) {
return false
}
}
return true
}(key, oldKeys) {
toAdd = append(toAdd, key)
}
}
return toAdd
}

var badKeys []string

func (a *accounts) set() error {
var newKeys []windowsKeyJSON
for _, s := range strings.Split(a.newMetadata.Instance.Attributes.WindowsKeys, "\n") {
var key windowsKeyJSON
if err := json.Unmarshal([]byte(s), &key); err != nil {
if !containsString(s, badReg) {
logger.Error(err)
badKeys = append(badKeys, s)
}
continue
}
if key.Exponent != "" && key.Modulus != "" && key.UserName != "" && !key.expired() {
newKeys = append(newKeys, key)
}
}

regKeys, err := readRegMultiString(regKeyBase, regName)
if err != nil && err != errRegNotExist {
return err
}

toAdd := compareAccounts(newKeys, regKeys)

for _, key := range toAdd {
creds, err := key.createOrResetPwd()
if err == nil {
printCreds(creds)
continue
}
logger.Error(err)
creds = &credsJSON{
PasswordFound: false,
Exponent: key.Exponent,
Modulus: key.Modulus,
UserName: key.UserName,
ErrorMessage: err.Error(),
}
printCreds(creds)
}

var jsonKeys []string
for _, key := range newKeys {
jsn, err := json.Marshal(key)
if err != nil {
// This *should* never happen as each key was just Unmarshalled above.
logger.Error(err)
continue
}
jsonKeys = append(jsonKeys, string(jsn))
}
return writeRegMultiString(regKeyBase, regName, jsonKeys)
}
108 changes: 108 additions & 0 deletions GCEAgent/accounts_test.go
@@ -0,0 +1,108 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.

package main

import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"encoding/base64"
"math/big"
"reflect"
"testing"
"time"
)

func TestExpired(t *testing.T) {
var tests = []struct {
sTime string
e bool
}{
{time.Now().Add(5 * time.Minute).Format(time.RFC3339), false},
{time.Now().Add(-5 * time.Minute).Format(time.RFC3339), true},
{"some bad time", true},
}

for _, tt := range tests {
k := windowsKeyJSON{ExpireOn: tt.sTime}
if tt.e != k.expired() {
t.Errorf("windowsKeyJSON.expired() with ExpiredOn %q should return %t", k.ExpireOn, tt.e)
}
}
}

func TestCreatecredsJSON(t *testing.T) {
pwd := "password"
prv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("error generating key: %v", err)
}
k := windowsKeyJSON{
Email: "email",
ExpireOn: "expire",
Exponent: base64.StdEncoding.EncodeToString(new(big.Int).SetInt64(int64(prv.PublicKey.E)).Bytes()),
Modulus: base64.StdEncoding.EncodeToString(prv.PublicKey.N.Bytes()),
UserName: "username",
}

c, err := createcredsJSON(k, pwd)
if err != nil {
t.Fatalf("error running createcredsJSON: %v", err)
}

bPwd, err := base64.StdEncoding.DecodeString(c.EncryptedPassword)
if err != nil {
t.Fatalf("error base64 decoding encoded pwd: %v", err)
}
decPwd, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, prv, bPwd, nil)
if err != nil {
t.Fatalf("error decrypting password: %v", err)
}
if pwd != string(decPwd) {
t.Errorf("decrypted password does not match expected, got: %s, want: %s", string(decPwd), pwd)
}
if k.UserName != c.UserName {
t.Errorf("returned credsJSON UserName field unexpected, got: %s, want: %s", c.UserName, k.UserName)
}
if !c.PasswordFound {
t.Error("returned credsJSON PasswordFound field is not true")
}
}

func TestCompareAccounts(t *testing.T) {
var tests = []struct {
newKeys []windowsKeyJSON
oldStrKeys []string
wantAdd []windowsKeyJSON
}{
// These should return toAdd:
// In MD, not Reg
{[]windowsKeyJSON{windowsKeyJSON{UserName: "foo"}}, nil, []windowsKeyJSON{windowsKeyJSON{UserName: "foo"}}},
{[]windowsKeyJSON{windowsKeyJSON{UserName: "foo"}}, []string{`{"UserName":"bar"}`}, []windowsKeyJSON{windowsKeyJSON{UserName: "foo"}}},

// These should return nothing:
// In Reg and Md
{[]windowsKeyJSON{windowsKeyJSON{UserName: "foo"}}, []string{`{"UserName":"foo"}`}, nil},
// In Md, not Reg
{nil, []string{`{UserName":"foo"}`}, nil},
}

for _, tt := range tests {
toAdd := compareAccounts(tt.newKeys, tt.oldStrKeys)
if !reflect.DeepEqual(tt.wantAdd, toAdd) {
t.Errorf("toAdd does not match expected: newKeys: %q, oldStrKeys: %q, got: %q, want: %q", tt.newKeys, tt.oldStrKeys, toAdd, tt.wantAdd)
}
}
}