コード例 #1
0
        public override async Task RunAsync(CancellationToken cancellationToken)
        {
            var now      = _clock.UtcNow;
            var manifest = SecretManifest.Read(_manifestFile);

            using var storage = _storageLocationTypeRegistry.Create(manifest.StorageLocation.Type, manifest.StorageLocation.Parameters);
            var existingSecrets = (await storage.ListSecretsAsync()).ToDictionary(p => p.Name);

            foreach (var(name, secret) in manifest.Secrets)
            {
                var secretType = _secretTypeRegistry.Create(secret.Type, secret.Parameters);
                var names      = secretType.GetCompositeSecretSuffixes().Select(suffix => name + suffix).ToList();
                var existing   = new List <SecretProperties>();
                foreach (var n in names)
                {
                    existingSecrets.TryGetValue(n, out var e);
                    existing.Add(e); // here we intentionally ignore the result of TryGetValue because we want to add null to the list to represent "this isn't in the store"
                }

                bool regenerate = false;

                if (existing.Any(e => e == null))
                {
                    // secret is missing from storage (either completely missing or partially missing)
                    regenerate = true;
                }
                else
                {
                    // If these fields aren't the same for every part of a composite secrets, assume the soonest value is right
                    var nextRotation = existing.Select(e => e.NextRotationOn).Min();
                    var expires      = existing.Select(e => e.ExpiresOn).Min();
                    if (nextRotation <= now)
                    {
                        // we have hit the rotation time, rotate
                        regenerate = true;
                    }
                    else if (expires <= now)
                    {
                        // the secret has expired, this shouldn't happen in normal operation but we should rotate
                        regenerate = true;
                    }
                }


                if (regenerate)
                {
                    var primary     = existing.FirstOrDefault(p => p != null);
                    var currentTags = primary?.Tags ?? ImmutableDictionary.Create <string, string>();
                    var context     = new RotationContext(currentTags);
                    var newValues   = await secretType.RotateValues(context, cancellationToken);

                    var newTags = context.GetValues();
                    foreach (var(n, value) in names.Zip(newValues))
                    {
                        await storage.SetSecretValueAsync(n, new SecretValue(value.Value, newTags, value.NextRotationOn, value.ExpiresOn));
                    }
                }
            }
        }
コード例 #2
0
        public override async Task RunAsync(CancellationToken cancellationToken)
        {
            try
            {
                _console.WriteLine($"Synchronizing secrets contained in {_manifestFile}");
                if (_force || _forcedSecrets.Any())
                {
                    bool confirmed = await _console.ConfirmAsync(
                        "--force or --force-secret is set, this will rotate one or more secrets ahead of schedule, possibly causing service disruption. Continue? ");

                    if (!confirmed)
                    {
                        return;
                    }
                }

                DateTimeOffset now      = _clock.UtcNow;
                SecretManifest manifest = SecretManifest.Read(_manifestFile);
                using StorageLocationType.Bound storage = _storageLocationTypeRegistry
                                                          .Get(manifest.StorageLocation.Type).BindParameters(manifest.StorageLocation.Parameters);
                using var disposables = new DisposableList();
                var references = new Dictionary <string, StorageLocationType.Bound>();
                foreach (var(name, storageReference) in manifest.References)
                {
                    var bound = _storageLocationTypeRegistry.Get(storageReference.Type)
                                .BindParameters(storageReference.Parameters);
                    disposables.Add(bound);
                    references.Add(name, bound);
                }

                Dictionary <string, SecretProperties> existingSecrets = (await storage.ListSecretsAsync()).ToDictionary(p => p.Name);

                List <(string name, SecretManifest.Secret secret, SecretType.Bound bound, HashSet <string> references)> orderedSecretTypes = GetTopologicallyOrderedSecrets(manifest.Secrets);
                var regeneratedSecrets = new HashSet <string>();

                foreach (var(name, secret, secretType, secretReferences) in orderedSecretTypes)
                {
                    _console.WriteLine($"Synchronizing secret {name}, type {secret.Type}");
                    List <string> names    = secretType.GetCompositeSecretSuffixes().Select(suffix => name + suffix).ToList();
                    var           existing = new List <SecretProperties>();
                    foreach (string n in names)
                    {
                        existingSecrets.Remove(n, out SecretProperties e);
                        existing.Add(e); // here we intentionally ignore the result of Remove because we want to add null to the list to represent "this isn't in the store"
                    }

                    bool regenerate = false;

                    if (_force)
                    {
                        _console.WriteLine("--force is set, will rotate.");
                        regenerate = true;
                    }
                    else if (_forcedSecrets.Contains(name))
                    {
                        _console.WriteLine($"--force-secret={name} is set, will rotate.");
                        regenerate = true;
                    }
                    else if (existing.Any(e => e == null))
                    {
                        _console.WriteLine("Secret not found in storage, will create.");
                        // secret is missing from storage (either completely missing or partially missing)
                        regenerate = true;
                    }
                    else if (regeneratedSecrets.Overlaps(secretReferences))
                    {
                        _console.WriteLine("Referenced secret was rotated, will rotate.");
                        regenerate = true;
                    }
                    else
                    {
                        // If these fields aren't the same for every part of a composite secrets, assume the soonest value is right
                        DateTimeOffset nextRotation = existing.Select(e => e.NextRotationOn).Min();
                        DateTimeOffset expires      = existing.Select(e => e.ExpiresOn).Min();
                        if (nextRotation <= now)
                        {
                            _console.WriteLine($"Secret scheduled for rotation on {nextRotation}, will rotate.");
                            // we have hit the rotation time, rotate
                            regenerate = true;

                            // since the rotation runs weekly, we need a 1 week grace period
                            // where verification runs will not fail, but rotation will happen.
                            // otherwise a secret scheduled for rotation on tuesday, will cause
                            // a build failure on wednesday, before it gets rotated normally on the following monday
                            // the verification mode is to catch the "the rotation hasn't happened in months" case
                            if (_verifyOnly && nextRotation > now.AddDays(-7))
                            {
                                _console.WriteLine("Secret is within verification grace period.");
                                regenerate = false;
                            }
                        }
                        if (expires <= now)
                        {
                            _console.WriteLine($"Secret expired on {expires}, will rotate.");
                            // the secret has expired, this shouldn't happen in normal operation but we should rotate
                            regenerate = true;
                        }
                    }

                    if (!regenerate)
                    {
                        _console.WriteLine("Secret is fine.");
                    }


                    if (regenerate && _verifyOnly)
                    {
                        _console.LogError($"Secret {name} requires rotation.");
                    }
                    else if (regenerate)
                    {
                        _console.WriteLine($"Generating new value(s) for secret {name}...");
                        SecretProperties primary = existing.FirstOrDefault(p => p != null);
                        IImmutableDictionary <string, string> currentTags = primary?.Tags ?? ImmutableDictionary.Create <string, string>();
                        var context = new RotationContext(name, currentTags, storage, references);
                        List <SecretData> newValues = await secretType.RotateValues(context, cancellationToken);

                        IImmutableDictionary <string, string> newTags = context.GetValues();
                        regeneratedSecrets.Add(name);
                        _console.WriteLine("Done.");
                        _console.WriteLine($"Storing new value(s) in storage for secret {name}...");
                        foreach (var(n, value) in names.Zip(newValues))
                        {
                            await storage.SetSecretValueAsync(n, new SecretValue(value.Value, newTags, value.NextRotationOn, value.ExpiresOn));
                        }

                        _console.WriteLine("Done.");
                    }
                }

                if (!_verifyOnly)
                {
                    foreach (var(name, key) in manifest.Keys)
                    {
                        await storage.EnsureKeyAsync(name, key);
                    }

                    foreach (var(name, value) in existingSecrets)
                    {
                        _console.LogWarning($"Extra secret '{name}' consider deleting it.");
                    }
                }
            }
            catch (FailWithExitCodeException)
            {
                throw;
            }
            catch (HumanInterventionRequiredException hire)
            {
                _console.LogError(hire.Message);
                throw new FailWithExitCodeException(42);
            }
            catch (Exception ex)
            {
                _console.LogError($"Unhandled Exception: {ex.Message}");
                throw new FailWithExitCodeException(-1);
            }
        }
コード例 #3
0
        public override async Task RunAsync(CancellationToken cancellationToken)
        {
            bool haveErrors    = false;
            var  manifestFiles = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            foreach (string manifestFile in _manifestFiles)
            {
                SecretManifest      manifest    = SecretManifest.Read(manifestFile);
                StorageLocationType storageType = _storageLocationTypeRegistry.Get(manifest.StorageLocation.Type);
                if (!(storageType is AzureKeyVault azureKeyVaultStorageType))
                {
                    _console.WriteImportant($"Skipping non-azure-key-vault manifest {manifestFile}", ConsoleColor.Yellow);
                    continue;
                }

                string vaultUri = azureKeyVaultStorageType.GetAzureKeyVaultUri(ParameterConverter.ConvertParameters <AzureKeyVaultParameters>(manifest.StorageLocation.Parameters));

                manifestFiles.Add(vaultUri, manifestFile);
            }


            var settingsFiles = new List <(FileInfo environmentFile, FileInfo baseFile)>();

            foreach (string jsonFile in Directory.EnumerateFiles(_basePath, "settings.json", SearchOption.AllDirectories))
            {
                var baseFile = new FileInfo(jsonFile);
                foreach (string envFile in Directory.EnumerateFiles(baseFile.DirectoryName, "settings.*.json"))
                {
                    settingsFiles.Add((new FileInfo(envFile), baseFile));
                }
            }

            foreach (var(envFile, baseFile) in settingsFiles)
            {
                string specifiedVaultUri =
                    await ReadVaultUriFromSettingsFile(envFile.FullName) ??
                    await ReadVaultUriFromSettingsFile(baseFile.FullName);

                if (string.IsNullOrEmpty(specifiedVaultUri))
                {
                    _console.LogError($"Settings file pair ({envFile}, {baseFile}) has no vault uri.", envFile.FullName, 0, 0);
                    haveErrors = true;
                    continue;
                }

                _console.WriteLine($"Settings file pair ({envFile}, {baseFile}) has vault uri {specifiedVaultUri}");

                if (!manifestFiles.TryGetValue(specifiedVaultUri, out string manifestFile))
                {
                    _console.LogError($"Vault Uri {specifiedVaultUri} does not have a matching manifest.", envFile.FullName, 0, 0);
                    haveErrors = true;
                    continue;
                }

                haveErrors |= !await _settingsFileValidator.ValidateFileAsync(envFile.FullName, baseFile.FullName, manifestFile, cancellationToken);
            }

            if (haveErrors)
            {
                throw new FailWithExitCodeException(77);
            }
        }