XRAPH/Writing/Secrets are not configuration with a different name

Secrets are not configuration with a different name

They have different lifetimes, different audiences and different failure modes. Treating them as one bag of strings is how credentials end up in a log line.

Published
Mar 2022
Length
2 min read
Systems
3

#The convenient mistake

Environment variables hold both. A database password and a page size arrive by the same mechanism, get read by the same function, and end up in the same struct. From the code's point of view they are strings.

They are not comparable. A page size can be logged, printed in a diagnostic endpoint, sent to an error tracker and committed to a repository with no consequence. A password cannot do any of those, and the moment they share a representation, the first accidental dump of the configuration struct sends credentials somewhere they cannot be recalled.

#Four differences that matter

Lifetime. Configuration changes when someone decides to change it. A secret has a rotation schedule, and a good system rotates on a timer whether or not anyone has decided anything.

Audience. Configuration should be visible to anyone operating the service. Secrets should be visible to as few principals as possible, which is least privilege doing its ordinary work .

History. Old configuration is uninteresting. Old secret versions matter, because a rotation with a grace window needs both the current and the previous value to validate, and an incident needs to know which version was live at a given time.

Deletion. Removing a configuration key is removing a key. Destroying a secret is a security operation with a definition of done, and media sanitisation guidance exists precisely because "we deleted it" is not the same as "it is unrecoverable" .

#What separating them buys

Once secrets are a distinct type, the compiler helps. A type whose String() method returns a redaction marker cannot be accidentally logged. A struct that holds one cannot be serialised into a diagnostic endpoint without an explicit unwrap that is easy to find in review.

1type Secret struct{ v string }
2
3func (s Secret) String() string { return "[redacted]" }
4func (s Secret) LogValue() slog.Value { return slog.StringValue("[redacted]") }
5func (s Secret) Reveal() string { return s.v } // grep for this in review

That is a small amount of code and it removes an entire class of incident.

#Rotation is the part people skip

Rotation only happens if it is boring. If rotating a credential requires a coordinated deploy, it will be done once at audit time and then not again.

The mechanism that makes it boring is versioning with a grace window. Write the new version, keep the previous one valid for a defined period, let consumers pick up the new one on their own schedule, then retire the old one on a date everybody agreed. That turns rotation from an event into a background process.

#The direction this points

Zero trust guidance pushes toward short-lived, narrowly scoped, automatically issued credentials rather than long-lived shared ones . Most systems I have worked on cannot get there in one step, and versioned rotation with a grace window is the step that makes the rest possible.

References

  1. [1]Jerome H. Saltzer, Michael D. Schroeder, The Protection of Information in Computer Systems, Proceedings of the IEEE, vol. 63, no. 9, pp. 1278-1308, 1975doi:10.1109/PROC.1975.9939
  2. [2]Scott Rose, Oliver Borchert, Stu Mitchell, Sean Connelly, Zero Trust Architecture, NIST Special Publication 800-207, 2020doi:10.6028/NIST.SP.800-207
  3. [3]Richard Kissel, Andrew Regenscheid, Matthew Scholl, Kevin Stine, Guidelines for Media Sanitization, NIST Special Publication 800-88 Revision 1, 2014doi:10.6028/NIST.SP.800-88r1