Skip to content

Commit aaf3d6e

Browse files
committed
add initial support for encrypted responses
Signed-off-by: Alex Suraci <[email protected]>
1 parent 7c6073b commit aaf3d6e

File tree

1 file changed

+134
-1
lines changed

1 file changed

+134
-1
lines changed

037-prototypes/proposal.md

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,12 @@ type MessageRequest struct {
175175
// The object to act on.
176176
Object Object `json:"object"`
177177

178-
// Configuration for handling TLS.
178+
// Configuration for establishing TLS connections.
179179
TLS TLSConfig `json:"tls,omitempty"`
180180

181+
// A base64-encoded 32-byte encryption key for use with AES-GCM.
182+
Encryption EncryptionConfig `json:"encryption,omitempty"`
183+
181184
// Path to a file into which the message handler must write its MessageResponses.
182185
ResponsePath string `json:"response_path"`
183186
}
@@ -189,6 +192,9 @@ type MessageResponse struct {
189192
// The object.
190193
Object Object `json:"object"`
191194

195+
// Encrypted fields of the object.
196+
Encrypted EncryptedObject `json:"encrypted"`
197+
192198
// Metadata to associate with the object. Shown to the user.
193199
Metadata []MetadataField `json:"metadata,omitempty"`
194200
}
@@ -203,6 +209,28 @@ type TLSConfig struct {
203209
SkipVerification bool `json:"skip_verification,omitempty"`
204210
}
205211

212+
type EncryptionConfig struct {
213+
// The encryption algorithm for the prototype to use.
214+
//
215+
// This value will be static, and changing it will imply a major bump to the
216+
// Prototype protocol version. It is included here as a helpful indicator so
217+
// that prototype authors don't have to guess at the payload.
218+
Algorithm string `json:"algorithm"`
219+
220+
// A base64-encoded 32-length key, unique to each message.
221+
Key []byte `json:"key"`
222+
}
223+
224+
// EncryptedObject contains an AES-GCM encrypted JSON payload containing
225+
// additional fields of the object.
226+
type EncryptedObject struct {
227+
// The base64-encoded encrypted payload.
228+
Payload []byte `json:"payload"`
229+
230+
// The base64-encrypted nonce.
231+
Nonce []byte `json:"nonce"`
232+
}
233+
206234
// MetadataField represents a named bit of metadata associated to an object.
207235
type MetadataField struct {
208236
Name string `json:"name"`
@@ -300,6 +328,111 @@ This response would be typical of a `check` that ran against a `git` repository
300328
that had three commits.
301329

302330

331+
## Encryption
332+
333+
In order to use Prototypes for credential acquisition, there must be a way to
334+
return object attributes which contain sensitive data without writing the data
335+
to disk in plaintext.
336+
337+
A Prototype's `MessageRequest` may contain an `EncryptionConfig` which
338+
specifies the encryption algorithm to use and any other necessary data for use
339+
with the algorithm (such as a key).
340+
341+
The Prototypes protcol will only support one encryption algorithm at a time,
342+
and if it needs to be changed, this will imply a **major** bump to the protocol
343+
version. This is to encourage phasing out support for no-longer-adequate
344+
security algorithms.
345+
346+
The decision as to which algorithm to use for the initial version is currently
347+
an open question, but the most likely candidate right now is AES-GCM, which is
348+
the same algorithm used for database encryption in Concourse. Another candidate
349+
may be NaCL. Note that whichever one we choose must be available in various
350+
languages so that Prototype authors aren't restricted to any particular library
351+
or language.
352+
353+
Assuming AES-GCM, the `EncryptionConfig` in the request will include a `key`
354+
field containing a base64-encoded 32-length key, and a `nonce_size` field
355+
indicating the size of the nonce necessary to encrypt/decrypt:
356+
357+
```json
358+
{
359+
"object": {
360+
"uri": "https://vault.example.com",
361+
"client_token": "01234567889abcdef"
362+
},
363+
"encryption": {
364+
"algorithm": "AES-GCM",
365+
"key": "aXzsY7eK/Jmn4L36eZSwAisyl6Q4LPFIVSGEE4XH0hA=",
366+
"nonce_size": 12
367+
}
368+
}
369+
```
370+
371+
372+
It is the responsibility of the Prototype implementation to generate a nonce
373+
value of the specified length. The Prototype would then marshal a JSON object
374+
containing fields to be encrypted, encrypt it with the key and nonce, and
375+
return the encrypted payload along with the nonce value in a `EncryptedObject`
376+
in the `MessageResponse` - both as base64-encoded values:
377+
378+
```json
379+
{
380+
"object": {
381+
"public": "fields"
382+
},
383+
"encrypted": {
384+
"nonce": "6rYKFHXh43khqsVs",
385+
"payload": "St5pRZumCx75d2x2s3vIjsClUi9DqgnIoG2Slt2RoCvz"
386+
}
387+
}
388+
```
389+
390+
The encrypted payload above is `{"some":"secret"}`, so the above response
391+
ultimately describes the following object:
392+
393+
```json
394+
{
395+
"public": "fields",
396+
"some": "secret"
397+
}
398+
```
399+
400+
### Rationale for encryption technique
401+
402+
A few alternatives to this approach were considered:
403+
404+
* Initially, the use of HTTPS was appealing as it would avoid placing data on
405+
disk entirely.
406+
407+
However this is a much more complicated architecture that raises many more
408+
questions:
409+
410+
* What are the API endpoints?
411+
* How are the requests routed?
412+
* How is TLS configured?
413+
* How is the response delivered?
414+
* Regular HTTP response? If the `web` node detaches, how can we re-attach?
415+
* Callbacks? What happens when the callback endpoint is down?
416+
* Is it safe to retry requests?
417+
418+
* We could write the responses to `tmpfs` to ensure they only ever exist in
419+
RAM.
420+
421+
The main downside is that operators would have to make sure that swap is
422+
disabled so that data is never written to disk. This seems like a safe
423+
assumption for Kubernetes but seems like an easy mistake to make for
424+
operators managing their own VMs.
425+
426+
One advantage of the current approach is that it tells Concourse which fields
427+
can be public and which fields contain sensitive information, while requiring
428+
the sensitive information to be encrypted.
429+
430+
While this could be supported either way by specifying a field such as
431+
`expose":["public"]`, it seems valuable to force Prototype authors to "do the
432+
right thing" and encrypt the sensitive data, rather than allowing them to
433+
simply hide it from the UI.
434+
435+
303436
## Object Cloning
304437

305438
Technically, all parts of the prototocol have been specified, but there is a

0 commit comments

Comments
 (0)