DEV Community

Common Fate
Common Fate

Posted on • Originally published at commonfate.io

Prevent Logging Secrets in Go by Using Custom Types

In our codebase for Granted Approvals, we’ve got a structure that looks like this:

type Provider struct {
    OrgUrl string
    APIToken string
}
Enter fullscreen mode Exit fullscreen mode

In this case, we’ve got a value named apiToken. It’s a secret, and we’d rather it doesn’t get leaked somewhere. Your secret value could also be an API token, or maybe it’s a password or a user address or anything else that you don’t want to get a Privacy Act lawsuit for.

The trouble with storing your secrets in strings like this is that it’s very easy to accidentally log them. It only takes one fmt or zap line for your secret value to be printed in plaintext and then possibly even sent to your external logging solutions. Using unexported fields can help, but Zap will still include them anyway.

If you’re reading this, then you probably already know why having sensitive data in your logs is dangerous. If you don’t, you can check out this. Or this. Or this. Or…. you get the point.

And if you’re rolling your eyes and saying I’d never log sensitive data, I’d ask: are you sure? You’re never going to accidentally type the wrong variable name in your print function? You’re never going to have a new dev join your team? Nobody on your team is ever going to be one night away from a deadline debugging why the hell the password reset function isn’t working and just go screw it, I don’t get paid enough to care about security anyway? I’m not so sure.

Our solution: custom types.

type Provider struct {
    OrgUrl StringValue
    APIToken SecretStringValue
}
Enter fullscreen mode Exit fullscreen mode

Now instead of orgUrl and apiToken being type string, they’re StringValue and SecretStringValue. Both of these custom types provide a small abstraction over a standard string type:


type StringValue struct {
    Value string
}

type SecretStringValue struct {
    Value string
}
Enter fullscreen mode Exit fullscreen mode

The part that’s important to us though is how they both implement the Stringer and MarshalJSON interfaces:

func (s StringValue) String() string {
    return s.Value
}

func (s StringValue) MarshalJSON() ([]byte, error) {
    return json.Marshal(s.String())
}

func (s SecretStringValue) String() string {
    return "*****"
}

func (s SecretStringValue) MarshalJSON() ([]byte, error) {
    return json.Marshal(s.String())
}
Enter fullscreen mode Exit fullscreen mode

The String() implementation for SecretStringValue will only return the redacted string “*****”, and same goes for the JSON. This makes it a whole lot more difficult to accidentally log the secret by just plugging it into fmt or zap.

So now, if you’re got something like:

oops := Provider{OrgURL:"commonfate.io",APIToken:"secret"}
fmt.Printf("%s", oops.APIToken)
Enter fullscreen mode Exit fullscreen mode

or:

oops := Provider{OrgURL: "commonfate.io", APIToken: "secret"}
b, _ := json.Marshal(oops)
fmt.Print(string(b))
Enter fullscreen mode Exit fullscreen mode

All you should hopefully get in return is ***** or {"OrgUrl":"commonfate.io","APIToken":"*****"}.

Is it totally foolproof? No. You can still log the secret if someone decides to deliberately print the Value, but at least now the team has clearer context on whether or not something is intended to be a Secret or just a regular Value. It makes it just that little bit harder to make a mistake — and in security, that’s all we can really ask.

If you want to check out the code for yourself, here’s a Go Playground link. And if you’re interested in seeing how we’ve implemented this in our own project, you can check out our gconfig package.

Top comments (3)

Collapse
 
ccoveille profile image
Christophe Colombier

Hi,

Instead of this:

type Provider struct {
    OrgURL string
    APIToken SecretStringValue
}

type StringValue struct {
    Value string
}

type SecretStringValue struct {
    Value string
}
Enter fullscreen mode Exit fullscreen mode

I would have used only

type SecretStringValue string
Enter fullscreen mode Exit fullscreen mode

You don't need the Value thing

go.dev/play/p/Yb0idhfxJFk

Collapse
 
ccoveille profile image
Christophe Colombier

Except the few comments I made, it's a good article.

Preventing to reveal secrets is always a good thing.

One remark if I may, the way your article is written could let think there is a need to define and use a StringValue for all strings used in Provider.

I think you just wanted to show the difference, but here a beginner might consider tge only way to apply your technique is to change all string to StringValue to make Provider works as intended.

Collapse
 
ccoveille profile image
Christophe Colombier

There is a typo in your example, you switch from OrgURL to OrgUrl depending on the line.