Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support of Environment Variables Import #5

Open
wants to merge 2 commits into
base: release/0.4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ The configuration file utilizes YAML objects to describe the secret literals, wh
| accessModifier | String | The access-level modifier of the generated Swift property containing obfuscated secret literal's data. The supported values are `internal` and `public`. If not specified, the top-level `defaultAccessModifier` value is used. See [Access control](#access-control) section for usage details. |
| name | String | The name of the generated Swift property containing obfuscated secret literal's data. This value is used as-is, without validity checking. Thus, make sure to use a valid property name.<br/><sub>**Required.**</sub> |
| namespace | String | The namespace in which to enclose the generated secret literal declaration. See [Namespaces](#namespaces) section for usage details. |
| value | String or List of strings | The plain value of the secret literal, which is to be obfuscated. The YAML data types are mapped to `String` and `Array<String>` in Swift, respectively.<br/><sub>**Required.**</sub> |
| environmentKey | String | The environment variable name to be used to retrieve the plain value of the secret literal. If not specified, the `value` is used. |
| value | String or List of strings | The plain value of the secret literal, which is to be obfuscated. The YAML data types are mapped to `String` and `Array<String>` in Swift, respectively.<br/><sub>**Required if `environmentKey` is not specified.**</sub> |

<details>
<summary><strong>Example secret definition</strong></summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

extension CodingUserInfoKey {
public static let processInfoEnvironment = CodingUserInfoKey(rawValue: "processInfoEnvironment")!
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public extension Configuration {

extension Configuration {

struct Secret: Hashable, Decodable {
struct Secret: Hashable {
let name: String
let value: Value
let namespace: String?
Expand Down Expand Up @@ -65,3 +65,52 @@ private extension Configuration {
case secrets
}
}

extension Configuration.Secret: Decodable {
fileprivate enum CodingKeys: String, CodingKey {
case name
case value
case namespace
case accessModifier
case environmentKey
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

name = try container.decode(String.self, forKey: .name)
namespace = try container.decodeIfPresent(String.self, forKey: .namespace)
accessModifier = try container.decodeIfPresent(String.self, forKey: .accessModifier)
guard container.contains(.environmentKey) else {
value = try container.decode(Configuration.Secret.Value.self, forKey: .value)
return
}

guard let environment = decoder.userInfo[.processInfoEnvironment] else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: [CodingKeys.environmentKey],
debugDescription: "userInfo[CodingUserInfoKey.processInfoEnvironment] not set"
)
)
}

guard let keyedVariables = environment as? [String: String] else {
throw DecodingError.typeMismatch([String: String].self, DecodingError.Context(codingPath: [CodingKeys.environmentKey], debugDescription: "processInfoEnvironment expected to be of [String: String] type"))
}

let key = try container.decode(String.self, forKey: CodingKeys.environmentKey)

guard let environmentValue = keyedVariables[key] else {
throw DecodingError.keyNotFound(
CodingKeys.environmentKey,
DecodingError.Context(
codingPath: [CodingKeys.environmentKey],
debugDescription: "environment must containn value for key `\(key)`"
)
)
}

value = .singleValue(environmentValue)
}
}
8 changes: 7 additions & 1 deletion Sources/swift-confidential/Subcommands/Obfuscate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ extension SwiftConfidential {
}

let configurationYAML = try Data(contentsOf: configuration)
let configuration = try YAMLDecoder().decode(Configuration.self, from: configurationYAML)
let configuration = try YAMLDecoder().decode(
Configuration.self,
from: configurationYAML,
userInfo: [
CodingUserInfoKey.processInfoEnvironment: ProcessInfo.processInfo.environment
]
)

var sourceSpecification = try Parsers.ModelTransform.SourceSpecification()
.parse(configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final class ConfigurationTests: XCTestCase {
""".utf8
)
let decoder = JSONDecoder()
decoder.userInfo[CodingUserInfoKey.processInfoEnvironment] = [:]

// when & then
var configuration: Configuration?
Expand Down Expand Up @@ -131,4 +132,51 @@ final class ConfigurationTests: XCTestCase {
result
)
}

func test_givenSecretSingleEnvironmentalValue_whenDecodeWithJSONDecoder_thenNoThrowAndReturnsExpectedValueInstance() throws {
// given
let environmentKey = "foo"
let environmentValue = "bar"
let environment = [environmentKey : environmentValue]
let secret = ("secret", environmentKey, environmentValue)
let jsonValue = try XCTUnwrap(
"""
{ "name": "\(secret.0)", "environmentKey": "\(secret.1)" }
""".data(using: .utf8)
)
let decoder = JSONDecoder()
decoder.userInfo[.processInfoEnvironment] = environment

// when
let decodedSecret = try decoder.decode(Configuration.Secret.self, from: jsonValue)

// then
XCTAssertEqual(
Configuration.Secret(
name: secret.0,
value: .singleValue(secret.2),
namespace: .none,
accessModifier: .none
),
decodedSecret
)
}

func test_givenSecretSingleMissingEnvironmentalValue_whenDecodeWithJSONDecoder_thenThrowsError() throws {

// given
let environmentKey = "foo"
XCTAssertNil(ProcessInfo.processInfo.environment[environmentKey])
let secret = ("secret", environmentKey)
let jsonValue = try XCTUnwrap(
"""
{ "name": "\(secret.0)", "environmentKey": "\(secret.1)" }
""".data(using: .utf8)
)
let decoder = JSONDecoder()
decoder.userInfo[.processInfoEnvironment] = [:]

// when
XCTAssertThrowsError(try decoder.decode(Configuration.Secret.self, from: jsonValue))
}
}