Loading credentials from Env variables

Good morning,

I’ve been developing a small server-side Swift project that requires the use of an API, and so access to an API Token on the server-side. I was wondering if there’s a way to retrieve this token as a string from a bash environmental variable, this would mean I can make the source code public and still save the credential in an env variable for CI or deployment.

I’m open to any alternatives. I basically just wanted a semi-automated way that would let me use an API token (String) in my code without having it hardcoded.

Thank you,
Tiago

import Foundation
let v = ProcessInfo.processInfo.environment["MYVAR"]
2 Likes

Take a look at https://github.com/IBM-Swift/Configuration. I use it in my server-side projects to load settings from various sources (incl. environment variables).

1 Like

I came across this on StackOverflow but it didn’t work. I believe this is for Xcode env variables? I’m trying to do this with a SwiftPM only project. No Xcode in Linux :)

Thanks I’ve been trying to use it with env variables, although I’m not sure I understand the syntax for env variables. What exactly do they mean by PATH__TO__CONFIGURATION=value? Say, how would I read an env variable called API_KEY?

Thanks a lot for your time.

No, ProcessInfo.processInfo.environment does return environment variables from the inherited shell.

$ export DEMO=-1
$ echo "import Foundation; print(ProcessInfo.processInfo.environment[\"DEMO\"])" > env.swift
$ swift env.swift 
Optional("-1")

Take in account that in the Swift REPL the environment variables will be clean.

1 Like

Oh interesting, I don’t know why but when I tried it it didn’t work. Thank you!

Configuration uses a double underscore to separate paths. This can be used to group settings in a hierarchy.

If your variable is called API_KEY, it doesn’t contain any path separators, so you should be able to read it with:

let settings = ConfigurationManager().load(.environmentVariables)
let apiKey = settings["API_KEY"] as? String

If your variable was named MYAPP__API_KEY, you would load it as:

let apiKey = settings["MYAPP:API_KEY"] as? String

I find hierarchies very useful as I load default settings (excluding secrets) from a JSON file and then overwrite (and add secrets) where needed via environment variables. For example:

Part of settings.json file:

{
  "DATABASE": {
    "URI": "something"
 }
}

I can then load this as follows:

let settings = ConfigurationManager()
        .load(file: "Configuration/settings.json", relativeFrom: .project)
        .load(.environmentVariables)
let uri = settings["DATABASE:URI"] as? String

And if needed, I can overwrite this by setting a DATABASE__URI environment variable.

I am using this technique in a project I hope to open source soon.

1 Like

Thanks a lot for everyone’s help.
I was able to get everything working with the Foundation ProcessInfo.
IBM’s Configuration seems amazing, especially for config files, but in my case, I just need to read this one env variable and as my code doesn’t run on REPL, Foundation suffices. I might use it on another bigger project though, have been looking for something like this for quite a while.

Thanks again,
Tiago

Any tricks for getting this to work? I have tried starting xcode from my shell, running my build from command line with xcodebuild, and nothing seems to pick up the environment variable.

Any tricks for getting this to work?

Are you building an app? Or a command-line tool?

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

I found my self here with the same question and I think some of the confusion comes from Xcode. If I run a swift project in Xcode I get the Environment Variables from the schemeplbus some Xcode ones.

If I build and run using swift build followed by run I get the system environment variables.

My next session in life is to work out how to get he system environment variables when I run in Xcode.

The simplest solution here is to add the required environment variables to your scheme. Is there a reason that won’t work for you?

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

the idea is pass in secrets via environment variables adding them to the Xcode schema means the secret will end up in git

1 Like

adding them to the Xcode schema means the secret will end up in git

IIRC, only shared schemes end up getting committed into Git.

Alternatively, one common practice is to support passing in credentials via a command line argument with a prefix that denotes where to get them. For example:

  • --password ENV:xxx gets the password from the xxx environment variable

  • --password FILE:xxx gets it from a file

  • --password KEYCHAIN:xxx gets it from a keychain item

  • --password STDIN gets it from stdin

ps I’m not a fan of passing credentials in via an environment variable because, on the Mac at least, you can view (the initial) environment variables of any process using ps.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Environment variables on any platform are not safe in memory. I agree long term not ideal. I will eventually add a platform specific secrets manager. For the time being:

-password FILE:xxx

Looks like it will work.

You are a gem thanks @eskimo

In my macOS command line app, I would like to access some environment vars when running in continuous integration. Can I use processInfo to read vars without defining them in the scheme?
e.g. given a command line app foo.out,

export var=1
./foo.out
In foo.out:
print(ProcessInfo.processInfo[var])

Can I use processInfo to read vars without defining them in the
scheme?

Maybe I’m reading more into this than I should be, but it seems like you’re missing the point of schemes:

  • ProcessInfo.processInfo.environment lets you read and write the process’s environment.

  • The scheme lets you add items to the environment when the code is run by Xcode.

If you build a command-line tool and then run it outside of Xcode, the environment settings of the scheme are irrelevant. For example, if you build a command-line tool with this in main.swift:

import Foundation

print(ProcessInfo.processInfo.environment["Hello"] ?? "-")

you can then run it in Terminal as follows:

% ./MyTool 
-
% Hello="Cruel World" ./MyTool 
Cruel World

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple