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

Commit

Permalink
Implement access control for baggage items via key properties
Browse files Browse the repository at this point in the history
  • Loading branch information
ktoso committed Oct 7, 2020
1 parent 67ac1f8 commit 8bcc8be
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 7 deletions.
19 changes: 17 additions & 2 deletions Sources/CoreBaggage/Baggage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ extension Baggage {
return self._storage.count
}

/// Returns true if the baggage has no items stored.
public var isEmpty: Bool {
return self._storage.isEmpty
}
Expand All @@ -193,12 +194,26 @@ extension Baggage {
///
/// Order of those invocations is NOT guaranteed and should not be relied on.
///
/// ### Access Control
///
/// By default accesses ALL `public` and `publicExceptLogging` values.
/// Baggage items whose keys are marked with `private` access, are never accessed by a forEach.
///
/// - Parameter body: A closure invoked with the type erased key and value stored for the key in this baggage.
public func forEach(_ body: (AnyBaggageKey, Any) throws -> Void) rethrows {
public func forEach(access: ForEachAccessType = .allPublic, _ body: (AnyBaggageKey, Any) throws -> Void) rethrows {
try self._storage.forEach { key, value in
try body(key, value)
if key.access.rawValue <= access.rawValue {
try body(key, value)
}
}
}

public enum ForEachAccessType: Int, Hashable {
/// Access all accessible values (i.e. `public` and `publicExceptLogging` values; `private` values are skipped)
case allPublic = 1
/// Access all accessible values for purposes of logging (i.e. ONLY `public`, skipping `publicExceptLogging` and `private` values)
case logging = 0
}
}

extension Baggage: CustomStringConvertible {
Expand Down
56 changes: 56 additions & 0 deletions Sources/CoreBaggage/BaggageAccessPolicy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Distributed Tracing Baggage open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift Distributed Tracing Baggage project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// Configures the policy related to values stored using this key.
///
/// This can be used to ensure that a value should never be logged automatically by a logger associated to a context.
///
/// Summary:
/// - `public` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
/// and will be logged by the `DefaultContext` `logger.
/// - `publicExceptLogging` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
/// however will NOT be logged by the `DefaultContext` `logger.
/// - `private` - items are NOT accessible by other modules via `baggage.forEach` nor are they logged by default.
/// The only way to gain access to a private baggage item is through it's key or accessor, which means that
/// access is controlled using Swift's native access control mechanism, i.e. a `private`/`internal` `Key` and `set` accessor,
/// will result in a baggage item that may only be set by the owning module, but read by anyone via the (`public`) accessor.
public enum BaggageAccessPolicy: Int, Hashable {
/// Access to this baggage item is NOT restricted.
/// This baggage item will be listed when `baggage.forEach` is invoked, and thus modules other than the defining
/// module may gain access to it and potentially log or pass it to other parts of the system.
///
/// Note that this can happen regardless of the key being declared private or internal.
///
/// ### Example
/// When module `A` defines `AKey` and keeps it `private`, any other module still may call `baggage.forEach`
case `public` = 0

/// Access to this baggage item is NOT restricted, however the `DefaultContext` (and any other well-behaved context)
/// MUST NOT log this baggage item.
///
/// This policy can be useful if some user sensitive value must be carried in baggage context, however it should never
/// appear in log statements. While usually such items should not be put into baggage, we offer this mode as a way of
/// threading through a system values which should not be logged nor pollute log statements.
case publicExceptLogging = 1

/// Access to this baggage item is RESTRICTED and can only be performed by a direct subscript lookup into the baggage.
///
/// This effectively restricts the access to the baggage item, to any party which has access to the associated
/// `BaggageKey`. E.g. if the baggage key is defined internal or private, and the `set` accessor is also internal or
/// private, no other module would be able to modify this baggage once it was set on a baggage context.
case `private` = 2
}

extension Baggage {
public typealias AccessPolicy = BaggageAccessPolicy
}
27 changes: 27 additions & 0 deletions Sources/CoreBaggage/BaggageKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,37 @@ public protocol BaggageKey {
///
/// Defaults to `nil`.
static var nameOverride: String? { get }

/// Configures the policy related to values stored using this key.
///
/// This can be used to ensure that a value should never be logged automatically by a logger associated to a context.
///
/// Summary:
/// - `public` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
/// and will be logged by the `DefaultContext` `logger.
/// - `publicExceptLogging` - items are accessible by other modules via `baggage.forEach` and direct key lookup,
/// however will NOT be logged by the `DefaultContext` `logger.
/// - `private` - items are NOT accessible by other modules via `baggage.forEach` nor are they logged by default.
/// The only way to gain access to a private baggage item is through it's key or accessor, which means that
/// access is controlled using Swift's native access control mechanism, i.e. a `private`/`internal` `Key` and `set` accessor,
/// will result in a baggage item that may only be set by the owning module, but read by anyone via the (`public`) accessor.
///
/// Defaults to `.public`.
static var access: Baggage.AccessPolicy { get }
}

extension BaggageKey {
public static var nameOverride: String? { return nil }
public static var access: Baggage.AccessPolicy { return .public }
}

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

public let access: Baggage.AccessPolicy

private let _nameOverride: String?

/// A human-readable String representation of the underlying key.
Expand All @@ -75,6 +95,13 @@ public struct AnyBaggageKey {
init<Key>(_ keyType: Key.Type) where Key: BaggageKey {
self.keyType = keyType
self._nameOverride = keyType.nameOverride
self.access = keyType.access
}
}

extension AnyBaggageKey: CustomStringConvertible {
public var description: String {
return "AnyBaggageKey(\(self.name), access: \(self.access))"
}
}

Expand Down
2 changes: 2 additions & 0 deletions Tests/CoreBaggageTests/BaggageTests+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ extension BaggageTests {
("testEmptyBaggageDescription", testEmptyBaggageDescription),
("testSingleKeyBaggageDescription", testSingleKeyBaggageDescription),
("testMultiKeysBaggageDescription", testMultiKeysBaggageDescription),
("test_forEach_forLogging_respects_BaggageAccessPolicy", test_forEach_forLogging_respects_BaggageAccessPolicy),
("test_forEach_respects_BaggageAccessPolicy", test_forEach_respects_BaggageAccessPolicy),
("test_todo_context", test_todo_context),
("test_topLevel", test_topLevel),
]
Expand Down
87 changes: 82 additions & 5 deletions Tests/CoreBaggageTests/BaggageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ final class BaggageTests: XCTestCase {
func testMultiKeysBaggageDescription() {
var baggage = Baggage.topLevel
baggage.testID = 42
baggage[SecondTestIDKey.self] = "test"
baggage[Baggage.SecondTestIDKey.self] = "test"

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

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Access Policy

func test_forEach_forLogging_respects_BaggageAccessPolicy() {
var baggage = Baggage.topLevel

baggage.testID = 42
baggage.publicExceptLogging = "dont-log-me"
baggage.private = "dont-log-me-either-not-even-foreach"

XCTAssertEqual(baggage.testID, 42)
XCTAssertEqual(baggage.publicExceptLogging, "dont-log-me")
XCTAssertEqual(baggage.private, "dont-log-me-either-not-even-foreach")

var count = 0
baggage.forEach(access: .logging) { key, value in
print(" access: \(key) = \(value)")
XCTAssertNotEqual(key.name, "\(Baggage.PublicExceptLoggingKey.self)")
XCTAssertNotEqual(key.name, "\(Baggage.PrivateKey.self)")
count += 1
}
XCTAssertEqual(count, 1)
}

func test_forEach_respects_BaggageAccessPolicy() {
var baggage = Baggage.topLevel

baggage.testID = 42
baggage.publicExceptLogging = "dont-log-me"
baggage.private = "dont-log-me-either-not-even-foreach"

XCTAssertEqual(baggage.testID, 42)
XCTAssertEqual(baggage.publicExceptLogging, "dont-log-me")
XCTAssertEqual(baggage.private, "dont-log-me-either-not-even-foreach")

var count = 0
baggage.forEach { key, value in
print(" access: \(key) = \(value)")
// the "dont log" may still appear here; but our `DefaultLoggingContext` in can handle it properly (other package)
XCTAssertNotEqual(key.name, "\(Baggage.PrivateKey.self)")
count += 1
}
XCTAssertEqual(count, 2)
}

// ==== ------------------------------------------------------------------------------------------------------------
// MARK: Factories

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

private extension Baggage {
extension Baggage {
var testID: Int? {
get {
return self[TestIDKey.self]
Expand All @@ -92,10 +137,42 @@ private extension Baggage {
self[TestIDKey.self] = newValue
}
}

enum SecondTestIDKey: Baggage.Key {
typealias Value = String

static let nameOverride: String? = "ExplicitKeyName"
}
}

private enum SecondTestIDKey: Baggage.Key {
typealias Value = String
extension Baggage {
var publicExceptLogging: String? {
get {
return self[PublicExceptLoggingKey.self]
}
set {
self[PublicExceptLoggingKey.self] = newValue
}
}

static let nameOverride: String? = "ExplicitKeyName"
var `private`: String? {
get {
return self[PrivateKey.self]
}
set {
self[PrivateKey.self] = newValue
}
}

enum PublicExceptLoggingKey: Baggage.Key {
typealias Value = String
static let nameOverride: String? = "public-except-logging"
static let access: BaggageAccessPolicy = .publicExceptLogging
}

enum PrivateKey: Baggage.Key {
typealias Value = String
static let nameOverride: String? = "private-DONT_SHOW_IN_LOGS_PLEASE"
static let access: BaggageAccessPolicy = .private
}
}

0 comments on commit 8bcc8be

Please sign in to comment.