shithub: s3

Download patch

ref: 338380c654e61a28d401869ec3ec30ad1cb85d4d
parent: 2a5f14382682a06a71faa31ccac61c2b829fee96
author: Jacob Moody <moody@posixcafe.org>
date: Thu Sep 18 14:36:36 EDT 2025

factotum: store multiple keys, specified by access=

--- a/cmd.c
+++ b/cmd.c
@@ -33,19 +33,19 @@
 }
 
 static void
-getkey(char *date, char *region, char *service, uchar out[SHA2_256dlen])
+getkey(char *date, char *region, char *service, char *access, uchar out[SHA2_256dlen])
 {
 	int fd;
 	AuthRpc *rpc;
 	char buf[256];
 	int n;
-	char keyspec[] = "proto=aws4";
 
 	fd = open("/mnt/factotum/rpc", ORDWR);
 	if(fd < 0)
 		sysfatal("factotum rpc open: %r");
 	rpc = auth_allocrpc(fd);
-	if(auth_rpc(rpc, "start", keyspec, strlen(keyspec)) != ARok)
+	n = snprint(buf, sizeof buf, "proto=aws4 access=%s", access);
+	if(auth_rpc(rpc, "start", buf, n) != ARok)
 		sysfatal("auth_rpc: %r");
 	n = snprint(buf, sizeof buf, "%s %s %s", date, region, service);
 	if(auth_rpc(rpc, "write", buf, n) != ARok)
@@ -85,7 +85,7 @@
 	sha2_256((uchar*)req, strlen(req), key, nil);
 	snprint(buf, sizeof buf, "%s\n%s\n%s/%s/%s/aws4_request\n%.*lH",
 		"AWS4-HMAC-SHA256", hreq->time, date, s3->region, "s3", SHA2_256dlen, key);
-	getkey(date, s3->region, "s3", key);
+	getkey(date, s3->region, "s3", s3->access, key);
 	hmac_sha2_256((uchar*)buf, strlen(buf), key, SHA2_256dlen, sig, nil);
 
 	snprint(hreq->authhdr, sizeof hreq->authhdr, "%s Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%.*lH",
--- a/factotum.c
+++ b/factotum.c
@@ -8,8 +8,87 @@
 #include <libsec.h>
 
 static char *user;
-static Attr *creds;
+static Attr *keyring[32];
 
+static Attr*
+findkey(char *key, char *value)
+{
+	int i;
+	char *p;
+
+	for(i = 0; i < nelem(keyring); i++){
+		if(keyring[i] == nil)
+			continue;
+		if((p = _strfindattr(keyring[i], key)) == nil)
+			continue;
+		if(strcmp(p, value) != 0)
+			continue;
+		return keyring[i];
+	}
+	return nil;
+}
+
+static char*
+setkey(char *key, char *value, Attr *new)
+{
+	int i;
+	Attr **empty;
+	char *p;
+
+	empty = nil;
+	for(i = 0; i < nelem(keyring); i++){
+		if(empty == nil && keyring[i] == nil)
+			empty = &keyring[i];
+		if((p = _strfindattr(keyring[i], key)) == nil)
+			continue;
+		if(strcmp(p, value) != 0)
+			continue;
+		_freeattr(keyring[i]);
+		keyring[i] = _copyattr(new);
+		return nil;
+	}
+	if(empty == nil)
+		return "keyring full";
+	*empty = _copyattr(new);
+	return nil;
+}
+
+static char*
+delkey(char *key, char *value)
+{
+	int i;
+	char *p;
+
+	for(i = 0; i < nelem(keyring); i++){
+		if(keyring[i] == nil)
+			continue;
+		if((p = _strfindattr(keyring[i], key)) == nil)
+			continue;
+		if(strcmp(p, value) != 0)
+			continue;
+		_freeattr(keyring[i]);
+		keyring[i] = nil;
+		return nil;
+	}
+	return "no such key";
+}
+
+static long
+dumpkey(char *buf, long n)
+{
+	int i;
+	char *e, *p;
+
+	e = buf + n;
+	p = buf;
+	for(i = 0; i < nelem(keyring); i++){
+		if(keyring[i] == nil)
+			continue;
+		p = seprint(p, e, "%A\n", keyring[i]);
+	}
+	return p - buf;
+}
+
 enum {
 	Sneedparam,
 	Shaveparam,
@@ -18,6 +97,7 @@
 typedef struct State State;
 struct State {
 	uchar buf[SHA2_256dlen];
+	Attr *creds;
 	uint phase;
 };
 
@@ -42,7 +122,7 @@
 			return ARerror;
 		}
 		/* All lights are green */
-		snprint(buf, sizeof buf, "AWS4%s", _strfindattr(creds, "!secret"));
+		snprint(buf, sizeof buf, "AWS4%s", _strfindattr(s->creds, "!secret"));
 		hmac_sha2_256((uchar*)args[0], strlen(args[0]), (uchar*)buf, strlen(buf), s->buf, nil);
 		hmac_sha2_256((uchar*)args[1], strlen(args[1]), s->buf, SHA2_256dlen, (uchar*)buf, nil);
 		hmac_sha2_256((uchar*)args[2], strlen(args[2]), (uchar*)buf, SHA2_256dlen, s->buf, nil);
@@ -100,7 +180,7 @@
 		case AttrNameval:
 		case AttrDefault:
 			if(a->name[0] == '!')
-				fmtprint(fmt, first+" %q", a->name);
+				fmtprint(fmt, first+" %q=", a->name);
 			else
 				fmtprint(fmt, first+" %q=%q", a->name, a->val);
 			break;
@@ -212,7 +292,8 @@
 fsread(Req *r)
 {
 	ulong path;
-	char buf[512];
+	char buf[1024];
+	long n;
 	Xfid *x;
 
 	path = r->fid->qid.path;
@@ -222,8 +303,8 @@
 		respond(r, nil);
 		break;
 	case Qctl:
-		snprint(buf, sizeof buf, "%A\n", creds);
-		readstr(r, buf);
+		n = dumpkey(buf, sizeof buf);
+		readbuf(r, buf, n);
 		respond(r, nil);
 		break;
 	case Qrpc:
@@ -249,11 +330,12 @@
 {
 	ulong path;
 	char buf[512];
-	char *proto;
+	char *p;
 	Xfid *x;
 	uint res;
 	char outbuf[512];
 	uint noutbuf;
+	Attr *new, *k;
 
 	path = r->fid->qid.path;
 	r->ofcall.count = snprint(buf, sizeof buf, "%.*s", r->ifcall.count, r->ifcall.data);
@@ -260,18 +342,28 @@
 	switch(path){
 	case Qctl:
 		if(strncmp(buf, "key ", 4) == 0){
-			_freeattr(creds);
-			creds = _parseattr(buf+4);
-			if((proto = _strfindattr(creds, "proto")) == nil || strcmp(proto, "aws4") != 0)
+			new = _parseattr(buf+4);
+			if((p = _strfindattr(new, "proto")) == nil || strcmp(p, "aws4") != 0)
 				respond(r, "proto!=aws4");
-			else
-				respond(r, nil);
-		} else if(strncmp(buf, "delkey", 6) == 0){
-			_freeattr(creds);
-			creds = nil;
-			respond(r, nil);
-		} else
+			else if((p = _strfindattr(new, "!secret")) == nil || strlen(p) == 0)
+				respond(r, "no !secret=");
+			else if((p = _strfindattr(new, "access")) == nil || strlen(p) == 0)
+				respond(r, "no access=");
+			else {
+				respond(r, setkey("access", p, new));
+				_freeattr(new);
+			}
+		} else if(strncmp(buf, "delkey ", 7) == 0){
+			new = _parseattr(buf+7);
+			p = _strfindattr(new, "access");
+			if(p == nil || strlen(p) == 0){
+				respond(r, "access= not specified");
+				return;
+			}
+			respond(r, delkey("access", p));
+		} else {
 			respond(r, "unknown ctl msg");
+		}
 		memset(buf, 0, sizeof buf);
 		break;
 	case Qrpc:
@@ -280,9 +372,20 @@
 			respond(r, "error write while there's data for you to read");
 			return;
 		}
-		if(strncmp(buf, "start", 5) == 0)
-			res = ARok;
-		else if(strncmp(buf, "write ", 6) == 0)
+		if(strncmp(buf, "start ", 6) == 0){
+			new = _parseattr(buf+6);
+			p = _strfindattr(new, "access");
+			if(p == nil || strlen(p) == 0){
+				werrstr("%s", "no access=");
+				res = ARerror;
+			} else if((k = findkey("access", p)) == nil){
+				werrstr("%s", "can not find matching key");
+				res = ARerror;
+			} else {
+				x->proto.creds = k;
+				res = ARok;
+			}
+		} else if(strncmp(buf, "write ", 6) == 0)
 			res = aws4write(&x->proto, buf+6, r->ifcall.count-6);
 		else if(strncmp(buf, "read", 4) == 0){
 			noutbuf = sizeof outbuf;
--- a/test/factotum.c
+++ b/test/factotum.c
@@ -49,7 +49,8 @@
 	fd = open("/mnt/factotum/ctl", OWRITE);
 	if(fd < 0)
 		sysfatal("open: %r");
-	fprint(fd, "key proto=aws4 !secret=blah");
+	if(fprint(fd, "key proto=aws4 !secret=blah access=myid") < 0)
+		sysfatal("key write: %r");
 	close(fd);
 
 	fd = open("/mnt/factotum/rpc", ORDWR);
@@ -57,7 +58,7 @@
 		sysfatal("open: %r");
 
 	rpc = auth_allocrpc(fd);
-	ret = auth_rpc(rpc, "start", "proto=aws4", strlen("proto=aws4"));
+	ret = auth_rpc(rpc, "start", "proto=aws4 access=myid", strlen("proto=aws4 access=myid"));
 	if(ret != ARok)
 		sysfatal("start: %r");
 
--