Skip to content

Commit 834dbef

Browse files
committed
Add a program that can add a user's SSH key.
This should be run automatically during dev-environment setup.
1 parent 95ee516 commit 834dbef

File tree

8 files changed

+282
-8
lines changed

8 files changed

+282
-8
lines changed

Makefile

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ setup: .setup-complete
3030

3131
package: bin/darwin/Hologram-$(GIT_TAG).pkg bin/linux/hologram-$(GIT_TAG).deb bin/linux/hologram-server-$(GIT_TAG).deb
3232

33-
build: bin/darwin/hologram-server bin/linux/hologram-server bin/darwin/hologram-agent bin/linux/hologram-agent bin/darwin/hologram-cli bin/linux/hologram-cli
33+
build: bin/darwin/hologram-server bin/linux/hologram-server bin/darwin/hologram-agent bin/linux/hologram-agent bin/darwin/hologram-cli bin/linux/hologram-cli bin/darwin/hologram-authorize bin/linux/hologram-authorize
3434

3535
protocol/hologram.pb.go: protocol/hologram.proto
3636
protoc --go_out=. protocol/hologram.proto
@@ -52,6 +52,10 @@ bin/%/hologram-server: protocol/hologram.pb.go server/.deps server/*.go server/*
5252
@echo "Building server version $(GIT_TAG)$(GIT_DIRTY)"
5353
@cd server/bin; gox -osarch="$*/amd64" -output="../../bin/$*/hologram-server"
5454

55+
bin/%/hologram-authorize: protocol/hologram.pb.go tools/install/*.go log/*.go log/.deps transport/remote/*.go transport/remote/bindata.go
56+
@echo "Building SSH key updater version $(GIT_TAG)$(GIT_DIRTY)"
57+
@cd tools/install; gox -osarch="$*/amd64" -output="../../bin/$*/hologram-authorize"
58+
5559
bin/%/hologram-cli: protocol/hologram.pb.go cli/*/*.go log/*.go log/.deps transport/local/*.go cli/.deps
5660
@echo "Building CLI version $(GIT_TAG)$(GIT_DIRTY)"
5761
@cd cli/bin; gox -osarch="$*/amd64" -output="../../bin/$*/hologram-cli"
@@ -60,12 +64,13 @@ bin/ping: tools/ping/main.go log/*.go log/.deps
6064
@cd tools/ping; go build
6165
@mv tools/ping/ping bin/ping
6266

63-
bin/darwin/Hologram-%.pkg: bin/darwin/hologram-agent bin/darwin/hologram-cli agent/support/darwin/com.adroll.hologram*.plist agent/support/darwin/postinstall.sh
67+
bin/darwin/Hologram-%.pkg: bin/darwin/hologram-agent bin/darwin/hologram-cli bin/darwin/hologram-authorize agent/support/darwin/com.adroll.hologram*.plist agent/support/darwin/postinstall.sh
6468
@echo "Creating temporary directory for pkgbuild..."
6569
@mkdir -p pkg/darwin/{root,scripts}
6670
@mkdir -p ./pkg/darwin/root/{usr/bin,Library/{LaunchDaemons,LaunchAgents},etc/hologram}
6771
@cp ./bin/darwin/hologram-agent ./pkg/darwin/root/usr/bin/hologram-agent
6872
@cp ./bin/darwin/hologram-cli ./pkg/darwin/root/usr/bin/hologram
73+
@cp ./bin/darwin/hologram-authorize ./pkg/darwin/root/usr/bin/hologram-authorize
6974
@cp ./config/agent.json ./pkg/darwin/root/etc/hologram/agent.json
7075
@cp ./agent/support/darwin/hologram-boot.sh ./pkg/darwin/root/usr/bin/hologram-boot
7176
@cp ./agent/support/darwin/com.adroll.hologram-ip.plist ./pkg/darwin/root/Library/LaunchDaemons
@@ -104,13 +109,14 @@ bin/linux/hologram-server-%.deb: bin/linux/hologram-server server/after-install.
104109
-a amd64 \
105110
./
106111

107-
bin/linux/hologram-%.deb: bin/linux/hologram-cli bin/linux/hologram-agent
112+
bin/linux/hologram-%.deb: bin/linux/hologram-cli bin/linux/hologram-agent bin/linux/hologram-authorize
108113
@echo "Creating temporary directory for fpm..."
109114
@mkdir -p ./pkg/linux/hologram/{root,scripts}
110115
@mkdir -p ./pkg/linux/hologram/root/{usr/local/bin,etc/{hologram,init.d}}
111116
@cp ./config/agent.json ./pkg/linux/hologram/root/etc/hologram/agent.json
112117
@cp ./bin/linux/hologram-cli ./pkg/linux/hologram/root/usr/local/bin/hologram
113118
@cp ./bin/linux/hologram-agent ./pkg/linux/hologram/root/usr/local/bin/hologram-agent
119+
@cp ./bin/linux/hologram-authorize ./pkg/linux/hologram/root/usr/local/bin/hologram-authorize
114120
@cp ./agent/support/debian/after-install.sh ./pkg/linux/hologram/scripts/
115121
@cp ./agent/support/debian/before-remove.sh ./pkg/linux/hologram/scripts/
116122
@cp ./agent/support/debian/init.sh ./pkg/linux/hologram/root/etc/init.d/hologram-agent
@@ -132,10 +138,10 @@ test: protocol/hologram.pb.go server/.deps agent/.deps transport/remote/bindata.
132138
clean:
133139
rm -rf ./bin ./build
134140
sudo rm -rf ./pkg
135-
141+
136142
version:
137143
@echo "$(GIT_TAG)"
138144

139-
.PHONY: setup all build package clean test version
145+
.PHONY: setup all build package clean test version
140146

141147

protocol/hologram.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ message Message {
3232
ServerResponse serverResponse = 7;
3333
AgentRequest agentRequest = 8;
3434
AgentResponse agentResponse = 9;
35+
Success success = 10;
36+
Failure failure = 11;
3537
}
3638
}
3739

@@ -49,6 +51,7 @@ message ServerRequest {
4951
SSHChallengeResponse challengeResponse = 5;
5052
MFATokenResponse tokenResponse = 6;
5153
GetUserCredentials getUserCredentials = 7;
54+
AddSSHKey addSSHkey = 8;
5255
}
5356
}
5457

@@ -59,6 +62,12 @@ message AssumeRole {
5962

6063
message GetUserCredentials {}
6164

65+
message AddSSHKey {
66+
required string username = 1;
67+
required string passwordhash = 2;
68+
required string sshkeybytes = 3;
69+
}
70+
6271
message SSHChallengeResponse {
6372
required bytes signature = 1;
6473
required string format = 2;

server/bin/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func main() {
148148
os.Exit(1)
149149
}
150150

151-
serverHandler := server.New(ldapCache, credentialsService, config.AWS.DefaultRole, stats)
151+
serverHandler := server.New(ldapCache, credentialsService, config.AWS.DefaultRole, stats, ldapServer)
152152
server, err := remote.NewServer(config.Listen, serverHandler.HandleConnection)
153153

154154
// Wait for a signal from the OS to shutdown.

server/server.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ package server
1717

1818
import (
1919
"errors"
20+
"fmt"
2021
"github.com/AdRoll/hologram/log"
2122
"github.com/AdRoll/hologram/protocol"
2223
"github.com/goamz/goamz/sts"
24+
"github.com/nmcclain/ldap"
2325
"github.com/peterbourgon/g2s"
2426
"golang.org/x/crypto/ssh"
2527
"math/rand"
@@ -38,6 +40,7 @@ type server struct {
3840
credentials CredentialService
3941
stats g2s.Statter
4042
DefaultRole string
43+
ldapServer LDAPImplementation
4144
}
4245

4346
/*
@@ -139,6 +142,56 @@ func (sm *server) HandleServerRequest(m protocol.MessageReadWriteCloser, r *prot
139142
m.Write(makeCredsResponse(creds))
140143
return
141144
}
145+
} else if addSSHKeyMsg := r.GetAddSSHkey(); addSSHKeyMsg != nil {
146+
sm.stats.Counter(1.0, "messages.addSSHKeyMsg", 1)
147+
148+
// Search for the user specified in this request.
149+
sr := ldap.NewSearchRequest(
150+
"dc=keeponprovoking,dc=com",
151+
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
152+
fmt.Sprintf("(cn=%s)", addSSHKeyMsg.GetUsername()),
153+
[]string{"sshPublicKey", "cn", "userPassword"},
154+
nil)
155+
156+
user, err := sm.ldapServer.Search(sr)
157+
if err != nil {
158+
log.Error("Error trying to handle addSSHKeyMsg: %s", err.Error())
159+
return
160+
}
161+
162+
if len(user.Entries) == 0 {
163+
log.Error("User %s not found!", addSSHKeyMsg.GetUsername())
164+
return
165+
}
166+
167+
// Check their password.
168+
password := user.Entries[0].GetAttributeValue("userPassword")
169+
if password != addSSHKeyMsg.GetPasswordhash() {
170+
log.Error("Provided password for user %s does not match %s!", addSSHKeyMsg.GetUsername(), password)
171+
return
172+
}
173+
174+
// Check to see if this SSH key already exists.
175+
for _, k := range user.Entries[0].GetAttributeValues("sshPublicKey") {
176+
if k == addSSHKeyMsg.GetSshkeybytes() {
177+
log.Warning("User %s already has this SSH key. Doing nothing.", addSSHKeyMsg.GetUsername())
178+
successMsg := &protocol.Message{Success: &protocol.Success{}}
179+
m.Write(successMsg)
180+
return
181+
}
182+
}
183+
184+
mr := ldap.NewModifyRequest(user.Entries[0].DN)
185+
mr.Add("sshPublicKey", []string{addSSHKeyMsg.GetSshkeybytes()})
186+
err = sm.ldapServer.Modify(mr)
187+
if err != nil {
188+
log.Error("Could not modify LDAP user: %s", err.Error())
189+
return
190+
}
191+
192+
successMsg := &protocol.Message{Success: &protocol.Success{}}
193+
m.Write(successMsg)
194+
return
142195
}
143196
}
144197

@@ -225,11 +278,12 @@ func makeCredsResponse(creds *sts.Credentials) *protocol.Message {
225278
New returns a server that can be used as a handler for a
226279
MessageConnection loop.
227280
*/
228-
func New(a Authenticator, c CredentialService, r string, s g2s.Statter) *server {
281+
func New(a Authenticator, c CredentialService, r string, s g2s.Statter, l LDAPImplementation) *server {
229282
return &server{
230283
credentials: c,
231284
authenticator: a,
232285
stats: s,
233286
DefaultRole: r,
287+
ldapServer: l,
234288
}
235289
}

server/server_test.go

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import (
1919
"github.com/AdRoll/hologram/protocol"
2020
"github.com/AdRoll/hologram/server"
2121
"github.com/goamz/goamz/sts"
22+
"github.com/nmcclain/ldap"
2223
"github.com/peterbourgon/g2s"
2324
. "github.com/smartystreets/goconvey/convey"
2425
"golang.org/x/crypto/ssh"
2526
"io"
27+
"reflect"
2628
"testing"
2729
"time"
2830
)
@@ -76,10 +78,57 @@ func (*dummyCredentials) AssumeRole(user *server.User, role string) (*sts.Creden
7678
}, nil
7779
}
7880

81+
type DummyLDAP struct {
82+
username string
83+
password string
84+
sshKeys []string
85+
req *ldap.ModifyRequest
86+
}
87+
88+
func (l *DummyLDAP) Search(*ldap.SearchRequest) (*ldap.SearchResult, error) {
89+
return &ldap.SearchResult{
90+
Entries: []*ldap.Entry{
91+
&ldap.Entry{DN: "something",
92+
Attributes: []*ldap.EntryAttribute{
93+
&ldap.EntryAttribute{
94+
Name: "cn",
95+
Values: []string{l.username},
96+
},
97+
&ldap.EntryAttribute{
98+
Name: "userPassword",
99+
Values: []string{l.password},
100+
},
101+
&ldap.EntryAttribute{
102+
Name: "sshPublicKey",
103+
Values: l.sshKeys,
104+
},
105+
},
106+
},
107+
},
108+
}, nil
109+
}
110+
111+
func (l *DummyLDAP) Modify(mr *ldap.ModifyRequest) error {
112+
if reflect.DeepEqual(mr, l.req) {
113+
l.sshKeys = []string{"test"}
114+
}
115+
return nil
116+
}
117+
79118
func TestServerStateMachine(t *testing.T) {
119+
// This silly thing is needed for equality testing for the LDAP dummy.
120+
neededModifyRequest := ldap.NewModifyRequest("something")
121+
neededModifyRequest.Add("sshPublicKey", []string{"test"})
122+
80123
Convey("Given a state machine setup with a null logger", t, func() {
81124
authenticator := &DummyAuthenticator{&server.User{Username: "words"}}
82-
testServer := server.New(authenticator, &dummyCredentials{}, "default", g2s.Noop())
125+
ldap := &DummyLDAP{
126+
username: "ari.adair",
127+
password: "098f6bcd4621d373cade4e832627b4f6",
128+
sshKeys: []string{},
129+
req: neededModifyRequest,
130+
}
131+
testServer := server.New(authenticator, &dummyCredentials{}, "default", g2s.Noop(), ldap)
83132
r, w := io.Pipe()
84133

85134
testConnection := protocol.NewMessageConnection(ReadWriter(r, w))
@@ -180,5 +229,41 @@ func TestServerStateMachine(t *testing.T) {
180229
So(credsMsg.GetServerResponse().GetVerificationFailure(), ShouldNotBeNil)
181230
})
182231
})
232+
233+
Convey("When a request to add an SSH key comes in", func() {
234+
user := "ari.adair"
235+
password := "098f6bcd4621d373cade4e832627b4f6"
236+
sshKey := "test"
237+
testMessage := &protocol.Message{
238+
ServerRequest: &protocol.ServerRequest{
239+
AddSSHkey: &protocol.AddSSHKey{
240+
Username: &user,
241+
Passwordhash: &password,
242+
Sshkeybytes: &sshKey,
243+
},
244+
},
245+
}
246+
247+
testConnection.Write(testMessage)
248+
Convey("If this request is valid", func() {
249+
msg, err := testConnection.Read()
250+
if err != nil {
251+
t.Fatal(err)
252+
}
253+
254+
if msg.GetSuccess() == nil {
255+
t.Fail()
256+
}
257+
Convey("It should add the SSH key to the user.", func() {
258+
So(ldap.sshKeys[0], ShouldEqual, sshKey)
259+
Convey("If the user tries to add the same SSH key", func() {
260+
testConnection.Write(testMessage)
261+
Convey("It should not insert the same key twice.", func() {
262+
So(len(ldap.sshKeys), ShouldEqual, 1)
263+
})
264+
})
265+
})
266+
})
267+
})
183268
})
184269
}

server/usercache.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ This interface exists for testing purposes.
4646
*/
4747
type LDAPImplementation interface {
4848
Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
49+
Modify(*ldap.ModifyRequest) error
4950
}
5051

5152
/*

server/usercache_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ func (sls *StubLDAPServer) Search(s *ldap.SearchRequest) (*ldap.SearchResult, er
8585
}, nil
8686
}
8787

88+
func (*StubLDAPServer) Modify(*ldap.ModifyRequest) error {
89+
return nil
90+
}
91+
8892
func randomBytes(length int) []byte {
8993
buf := make([]byte, length)
9094

0 commit comments

Comments
 (0)