Skip to content
This repository was archived by the owner on Jun 6, 2023. It is now read-only.

Commit 551bfa2

Browse files
committed
Implement access control for baggage items via key properties
1 parent 67ac1f8 commit 551bfa2

File tree

5 files changed

+184
-7
lines changed

5 files changed

+184
-7
lines changed

Sources/CoreBaggage/Baggage.swift

+17-2
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ extension Baggage {
185185
return self._storage.count
186186
}
187187

188+
/// Returns true if the baggage has no items stored.
188189
public var isEmpty: Bool {
189190
return self._storage.isEmpty
190191
}
@@ -193,12 +194,26 @@ extension Baggage {
193194
///
194195
/// Order of those invocations is NOT guaranteed and should not be relied on.
195196
///
197+
/// ### Access Control
198+
///
199+
/// By default accesses ALL `public` and `publicExceptLogging` values.
200+
/// Baggage items whose keys are marked with `private` access, are never accessed by a forEach.
201+
///
196202
/// - Parameter body: A closure invoked with the type erased key and value stored for the key in this baggage.
197-
public func forEach(_ body: (AnyBaggageKey, Any) throws -> Void) rethrows {
203+
public func forEach(access: ForEachAccessType = .allPublic, _ body: (AnyBaggageKey, Any) throws -> Void) rethrows {
198204
try self._storage.forEach { key, value in
199-
try body(key, value)
205+
if key.access.rawValue <= access.rawValue {
206+
try body(key, value)
207+
}
200208
}
201209
}
210+
211+
public enum ForEachAccessType: Int, Hashable {
212+
/// Access all accessible values (i.e. `public` and `publicExceptLogging` values; `private` values are skipped)
213+
case allPublic = 8
214+
/// Access all accessible values for purposes of logging (i.e. ONLY `public`, skipping `publicExceptLogging` and `private` values)
215+
case logging = 0
216+
}
202217
}
203218

204219
extension Baggage: CustomStringConvertible {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing Baggage open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift Distributed Tracing Baggage project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
/// Configures the policy related to values stored using this key.
15+
///
16+
/// This can be used to ensure that a value should never be logged automatically by a logger associated to a context.
17+
///
18+
/// Summary:
19+
/// - `public` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
20+
/// and will be logged by the `LoggingContext` `logger.
21+
/// - `publicExceptLogging` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
22+
/// however will NOT be logged by the `LoggingContext` `logger`.
23+
/// - `private` - items are NOT accessible by other modules via `baggage.forEach` nor are they logged by default.
24+
/// The only way to gain access to a private baggage item is through it's key or accessor, which means that
25+
/// access is controlled using Swift's native access control mechanism, i.e. a `private`/`internal` `Key` and `set` accessor,
26+
/// will result in a baggage item that may only be set by the owning module, but read by anyone via the (`public`) accessor.
27+
public enum BaggageAccessPolicy: Int, Hashable {
28+
/// Access to this baggage item is NOT restricted.
29+
/// This baggage item will be listed when `baggage.forEach` is invoked, and thus modules other than the defining
30+
/// module may gain access to it and potentially log or pass it to other parts of the system.
31+
///
32+
/// Note that this can happen regardless of the key being declared private or internal.
33+
///
34+
/// ### Example
35+
/// When module `A` defines `AKey` and keeps it `private`, any other module still may call `baggage.forEach`
36+
case `public` = 0
37+
38+
/// Access to this baggage item is NOT restricted, however the `LoggingContext` (and any other well-behaved context)
39+
/// MUST NOT log this baggage item.
40+
///
41+
/// This policy can be useful if some user sensitive value must be carried in baggage context, however it should never
42+
/// appear in log statements. While usually such items should not be put into baggage, we offer this mode as a way of
43+
/// threading through a system values which should not be logged nor pollute log statements.
44+
case publicExceptLogging = 8
45+
46+
/// Access to this baggage item is RESTRICTED and can only be performed by a direct subscript lookup into the baggage.
47+
///
48+
/// This effectively restricts the access to the baggage item, to any party which has access to the associated
49+
/// `BaggageKey`. E.g. if the baggage key is defined internal or private, and the `set` accessor is also internal or
50+
/// private, no other module would be able to modify this baggage once it was set on a baggage context.
51+
case `private` = 16
52+
}
53+
54+
extension Baggage {
55+
public typealias AccessPolicy = BaggageAccessPolicy
56+
}

Sources/CoreBaggage/BaggageKey.swift

+27
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,37 @@ public protocol BaggageKey {
5353
///
5454
/// Defaults to `nil`.
5555
static var nameOverride: String? { get }
56+
57+
/// Configures the policy related to values stored using this key.
58+
///
59+
/// This can be used to ensure that a value should never be logged automatically by a logger associated to a context.
60+
///
61+
/// Summary:
62+
/// - `public` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
63+
/// and will be logged by the `LoggingContext` `logger.
64+
/// - `publicExceptLogging` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
65+
/// however will NOT be logged by the `LoggingContext` `logger.
66+
/// - `private` - items are NOT accessible by other modules via `baggage.forEach` nor are they logged by default.
67+
/// The only way to gain access to a private baggage item is through it's key or accessor, which means that
68+
/// access is controlled using Swift's native access control mechanism, i.e. a `private`/`internal` `Key` and `set` accessor,
69+
/// will result in a baggage item that may only be set by the owning module, but read by anyone via the (`public`) accessor.
70+
///
71+
/// Defaults to `.public`.
72+
static var access: Baggage.AccessPolicy { get }
5673
}
5774

5875
extension BaggageKey {
5976
public static var nameOverride: String? { return nil }
77+
public static var access: Baggage.AccessPolicy { return .public }
6078
}
6179

6280
/// A type-erased `BaggageKey` used when iterating through the `Baggage` using its `forEach` method.
6381
public struct AnyBaggageKey {
6482
/// The key's type represented erased to an `Any.Type`.
6583
public let keyType: Any.Type
6684

85+
public let access: Baggage.AccessPolicy
86+
6787
private let _nameOverride: String?
6888

6989
/// A human-readable String representation of the underlying key.
@@ -75,6 +95,13 @@ public struct AnyBaggageKey {
7595
init<Key>(_ keyType: Key.Type) where Key: BaggageKey {
7696
self.keyType = keyType
7797
self._nameOverride = keyType.nameOverride
98+
self.access = keyType.access
99+
}
100+
}
101+
102+
extension AnyBaggageKey: CustomStringConvertible {
103+
public var description: String {
104+
return "AnyBaggageKey(\(self.name), access: \(self.access))"
78105
}
79106
}
80107

Tests/CoreBaggageTests/BaggageTests+XCTest.swift

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ extension BaggageTests {
3030
("testEmptyBaggageDescription", testEmptyBaggageDescription),
3131
("testSingleKeyBaggageDescription", testSingleKeyBaggageDescription),
3232
("testMultiKeysBaggageDescription", testMultiKeysBaggageDescription),
33+
("test_forEach_forLogging_respects_BaggageAccessPolicy", test_forEach_forLogging_respects_BaggageAccessPolicy),
34+
("test_forEach_respects_BaggageAccessPolicy", test_forEach_respects_BaggageAccessPolicy),
3335
("test_todo_context", test_todo_context),
3436
("test_topLevel", test_topLevel),
3537
]

Tests/CoreBaggageTests/BaggageTests.swift

+82-5
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ final class BaggageTests: XCTestCase {
5555
func testMultiKeysBaggageDescription() {
5656
var baggage = Baggage.topLevel
5757
baggage.testID = 42
58-
baggage[SecondTestIDKey.self] = "test"
58+
baggage[Baggage.SecondTestIDKey.self] = "test"
5959

6060
let description = String(describing: baggage)
6161
XCTAssert(description.starts(with: "Baggage(keys: ["), "Was: \(description)")
@@ -64,6 +64,51 @@ final class BaggageTests: XCTestCase {
6464
XCTAssert(description.contains("ExplicitKeyName"), "Was: \(description)")
6565
}
6666

67+
// ==== ----------------------------------------------------------------------------------------------------------------
68+
// MARK: Access Policy
69+
70+
func test_forEach_forLogging_respects_BaggageAccessPolicy() {
71+
var baggage = Baggage.topLevel
72+
73+
baggage.testID = 42
74+
baggage.publicExceptLogging = "dont-log-me"
75+
baggage.private = "dont-log-me-either-not-even-foreach"
76+
77+
XCTAssertEqual(baggage.testID, 42)
78+
XCTAssertEqual(baggage.publicExceptLogging, "dont-log-me")
79+
XCTAssertEqual(baggage.private, "dont-log-me-either-not-even-foreach")
80+
81+
var count = 0
82+
baggage.forEach(access: .logging) { key, value in
83+
print(" access: \(key) = \(value)")
84+
XCTAssertNotEqual(key.name, "\(Baggage.PublicExceptLoggingKey.self)")
85+
XCTAssertNotEqual(key.name, "\(Baggage.PrivateKey.self)")
86+
count += 1
87+
}
88+
XCTAssertEqual(count, 1)
89+
}
90+
91+
func test_forEach_respects_BaggageAccessPolicy() {
92+
var baggage = Baggage.topLevel
93+
94+
baggage.testID = 42
95+
baggage.publicExceptLogging = "dont-log-me"
96+
baggage.private = "dont-log-me-either-not-even-foreach"
97+
98+
XCTAssertEqual(baggage.testID, 42)
99+
XCTAssertEqual(baggage.publicExceptLogging, "dont-log-me")
100+
XCTAssertEqual(baggage.private, "dont-log-me-either-not-even-foreach")
101+
102+
var count = 0
103+
baggage.forEach { key, value in
104+
print(" access: \(key) = \(value)")
105+
// the "dont log" may still appear here; but our `DefaultLoggingContext` in can handle it properly (other package)
106+
XCTAssertNotEqual(key.name, "\(Baggage.PrivateKey.self)")
107+
count += 1
108+
}
109+
XCTAssertEqual(count, 2)
110+
}
111+
67112
// ==== ------------------------------------------------------------------------------------------------------------
68113
// MARK: Factories
69114

@@ -83,7 +128,7 @@ private enum TestIDKey: Baggage.Key {
83128
typealias Value = Int
84129
}
85130

86-
private extension Baggage {
131+
extension Baggage {
87132
var testID: Int? {
88133
get {
89134
return self[TestIDKey.self]
@@ -92,10 +137,42 @@ private extension Baggage {
92137
self[TestIDKey.self] = newValue
93138
}
94139
}
140+
141+
enum SecondTestIDKey: Baggage.Key {
142+
typealias Value = String
143+
144+
static let nameOverride: String? = "ExplicitKeyName"
145+
}
95146
}
96147

97-
private enum SecondTestIDKey: Baggage.Key {
98-
typealias Value = String
148+
extension Baggage {
149+
var publicExceptLogging: String? {
150+
get {
151+
return self[PublicExceptLoggingKey.self]
152+
}
153+
set {
154+
self[PublicExceptLoggingKey.self] = newValue
155+
}
156+
}
99157

100-
static let nameOverride: String? = "ExplicitKeyName"
158+
var `private`: String? {
159+
get {
160+
return self[PrivateKey.self]
161+
}
162+
set {
163+
self[PrivateKey.self] = newValue
164+
}
165+
}
166+
167+
enum PublicExceptLoggingKey: Baggage.Key {
168+
typealias Value = String
169+
static let nameOverride: String? = "public-except-logging"
170+
static let access: BaggageAccessPolicy = .publicExceptLogging
171+
}
172+
173+
enum PrivateKey: Baggage.Key {
174+
typealias Value = String
175+
static let nameOverride: String? = "private-DONT_SHOW_IN_LOGS_PLEASE"
176+
static let access: BaggageAccessPolicy = .private
177+
}
101178
}

0 commit comments

Comments
 (0)