Example #1
0
        protected Task ConfigureBootstrapper()
        {
            Console.WriteLine("Configuring bootstrapper.");
            DeviceProvisioningMethod method = this.dpsAttestation.Match(
                dps => { return(new DeviceProvisioningMethod(dps)); },
                () =>
            {
                IotHubConnectionStringBuilder builder =
                    IotHubConnectionStringBuilder.Create(this.context.IotHubConnectionString);
                Device device           = this.context.Device.Expect(() => new InvalidOperationException("Expected a valid device instance"));
                string connectionString =
                    $"HostName={builder.HostName};" +
                    $"DeviceId={device.Id};" +
                    $"SharedAccessKey={device.Authentication.SymmetricKey.PrimaryKey}";

                return(new DeviceProvisioningMethod(connectionString));
            });

            Option <string> agentImage = Option.None <string>();

            if (this.initializeWithAgentArtifact)
            {
                agentImage = Option.Some <string>(this.EdgeAgentImage());
            }

            return(this.bootstrapper.Configure(method, agentImage, this.hostname, this.parentHostname, this.deviceCaCert, this.deviceCaPk, this.deviceCaCerts, this.runtimeLogLevel));
        }
Example #2
0
        public async Task Configure(DeviceProvisioningMethod method, string image, string hostname, string deviceCaCert, string deviceCaPk, string deviceCaCerts, LogLevel runtimeLogLevel)
        {
            Console.WriteLine($"Setting up iotedgectl with agent image '{image}'");

            string connectionString = method.ManualConnectionString.Expect(() => new ArgumentException("The iotedgectl utility only supports device connection string to bootstrap Edge"));

            string registryArgs = this.credentials.Match(
                c => $"--docker-registries {c.Address} {c.User} {c.Password}",
                () => string.Empty);

            await Process.RunAsync(
                "iotedgectl",
                $"setup --connection-string \"{connectionString}\" --nopass {registryArgs} --image {image} --edge-hostname {hostname}",
                120);
        }
Example #3
0
        protected Task ConfigureBootstrapper()
        {
            Console.WriteLine("Configuring bootstrapper.");
            DeviceProvisioningMethod method = this.dpsAttestation.Match(
                dps => { return(new DeviceProvisioningMethod(dps)); },
                () =>
            {
                IotHubConnectionStringBuilder builder =
                    IotHubConnectionStringBuilder.Create(this.context.IotHubConnectionString);
                string connectionString =
                    $"HostName={builder.HostName};" +
                    $"DeviceId={this.context.Device.Id};" +
                    $"SharedAccessKey={this.context.Device.Authentication.SymmetricKey.PrimaryKey}";

                return(new DeviceProvisioningMethod(connectionString));
            });

            return(this.bootstrapper.Configure(method, this.EdgeAgentImage(), this.hostname, this.deviceCaCert, this.deviceCaPk, this.deviceCaCerts, this.runtimeLogLevel));
        }
Example #4
0
        public async Task Configure(
            DeviceProvisioningMethod method,
            Option <string> agentImage,
            string hostname,
            Option <string> parentHostname,
            string deviceCaCert,
            string deviceCaPk,
            string deviceCaCerts,
            LogLevel runtimeLogLevel)
        {
            agentImage.ForEach(
                image =>
            {
                Console.WriteLine($"Setting up aziot-edged with agent image {image}");
            },
                () =>
            {
                Console.WriteLine("Setting up aziot-edged with agent image 1.0");
            });

            const string KEYD      = "/etc/aziot/keyd/config.toml";
            const string CERTD     = "/etc/aziot/certd/config.toml";
            const string IDENTITYD = "/etc/aziot/identityd/config.toml";
            const string EDGED     = "/etc/aziot/edged/config.yaml";

            // Initialize each service's config file.
            // The mapped values are:
            // - Path to the config file (/etc/aziot/[service_name]/config.[toml | yaml])
            // - User owning the config file
            // - Template used to generate the config file.
            Dictionary <string, (string owner, IConfigDocument document)> config = new Dictionary <string, (string, IConfigDocument)>();

            config.Add(KEYD, ("aziotks", InitDocument(KEYD + ".default", true)));
            config.Add(CERTD, ("aziotcs", InitDocument(CERTD + ".default", true)));
            config.Add(IDENTITYD, ("aziotid", InitDocument(IDENTITYD + ".default", true)));
            config.Add(EDGED, ("iotedge", InitDocument(EDGED + ".template", false)));

            // Directory for storing keys; create it if it doesn't exist.
            string keyDir = "/var/secrets/aziot/keyd/";

            Directory.CreateDirectory(keyDir);
            SetOwner(keyDir, config[KEYD].owner, "700");

            // Need to always reprovision so previous test runs don't affect this one.
            config[IDENTITYD].document.RemoveIfExists("provisioning");
            config[IDENTITYD].document.ReplaceOrAdd("provisioning.always_reprovision_on_startup", true);

            method.ManualConnectionString.Match(
                cs =>
            {
                string keyPath = Path.Combine(keyDir, "device-id");
                config[IDENTITYD].document.ReplaceOrAdd("provisioning.source", "manual");
                config[IDENTITYD].document.ReplaceOrAdd("provisioning.authentication.method", "sas");
                config[IDENTITYD].document.ReplaceOrAdd("provisioning.authentication.device_id_pk", "device-id");
                config[KEYD].document.ReplaceOrAdd("preloaded_keys.device-id", $"file://{keyPath}");

                string[] segments = cs.Split(";");

                foreach (string s in segments)
                {
                    string[] param = s.Split("=", 2);

                    switch (param[0])
                    {
                    case "HostName":
                        // replace IoTHub hostname with parent hostname for nested edge
                        config[IDENTITYD].document.ReplaceOrAdd("provisioning.iothub_hostname", parentHostname.GetOrElse(param[1]));
                        break;

                    case "SharedAccessKey":
                        File.WriteAllBytes(keyPath, Convert.FromBase64String(param[1]));
                        SetOwner(keyPath, config[KEYD].owner, "600");
                        break;

                    case "DeviceId":
                        config[IDENTITYD].document.ReplaceOrAdd("provisioning.device_id", param[1]);
                        break;

                    default:
                        break;
                    }
                }

                return(string.Empty);
            },
                () =>
            {
                config[IDENTITYD].document.RemoveIfExists("provisioning");
                return(string.Empty);
            });

            method.Dps.ForEach(
                dps =>
            {
                config[IDENTITYD].document.ReplaceOrAdd("provisioning.source", "dps");
                config[IDENTITYD].document.ReplaceOrAdd("provisioning.global_endpoint", dps.EndPoint);
                config[IDENTITYD].document.ReplaceOrAdd("provisioning.scope_id", dps.ScopeId);
                switch (dps.AttestationType)
                {
                case DPSAttestationType.SymmetricKey:
                    string dpsKeyPath = Path.Combine(keyDir, "device-id");
                    string dpsKey     = dps.SymmetricKey.Expect(() => new ArgumentException("Expected symmetric key"));

                    File.WriteAllBytes(dpsKeyPath, Convert.FromBase64String(dpsKey));
                    SetOwner(dpsKeyPath, config[KEYD].owner, "600");

                    config[IDENTITYD].document.ReplaceOrAdd("provisioning.attestation.method", "symmetric_key");
                    config[IDENTITYD].document.ReplaceOrAdd("provisioning.attestation.symmetric_key", "device-id");

                    break;

                case DPSAttestationType.X509:
                    string certPath = dps.DeviceIdentityCertificate.Expect(() => new ArgumentException("Expected path to identity certificate"));
                    string keyPath  = dps.DeviceIdentityPrivateKey.Expect(() => new ArgumentException("Expected path to identity private key"));

                    SetOwner(certPath, config[CERTD].owner, "444");
                    SetOwner(keyPath, config[KEYD].owner, "400");

                    config[CERTD].document.ReplaceOrAdd("preloaded_certs.device-id", new Uri(certPath).AbsoluteUri);
                    config[KEYD].document.ReplaceOrAdd("preloaded_keys.device-id", new Uri(keyPath).AbsoluteUri);

                    config[IDENTITYD].document.ReplaceOrAdd("provisioning.attestation.method", "x509");
                    config[IDENTITYD].document.ReplaceOrAdd("provisioning.attestation.identity_cert", "device-id");
                    config[IDENTITYD].document.ReplaceOrAdd("provisioning.attestation.identity_pk", "device-id");
                    break;

                default:
                    break;
                }

                dps.RegistrationId.ForEach(id => { config[IDENTITYD].document.ReplaceOrAdd("provisioning.attestation.registration_id", id); });
            });

            agentImage.ForEach(image =>
            {
                config[EDGED].document.ReplaceOrAdd("agent.config.image", image);
            });

            config[EDGED].document.ReplaceOrAdd("hostname", hostname);
            config[IDENTITYD].document.ReplaceOrAdd("hostname", hostname);

            parentHostname.ForEach(v => config[EDGED].document.ReplaceOrAdd("parent_hostname", v));

            foreach (RegistryCredentials c in this.credentials)
            {
                config[EDGED].document.ReplaceOrAdd("agent.config.auth.serveraddress", c.Address);
                config[EDGED].document.ReplaceOrAdd("agent.config.auth.username", c.User);
                config[EDGED].document.ReplaceOrAdd("agent.config.auth.password", c.Password);
            }

            config[EDGED].document.ReplaceOrAdd("agent.env.RuntimeLogLevel", runtimeLogLevel.ToString());

            if (this.httpUris.HasValue)
            {
                HttpUris uris = this.httpUris.OrDefault();
                config[EDGED].document.ReplaceOrAdd("connect.management_uri", uris.ConnectManagement);
                config[EDGED].document.ReplaceOrAdd("connect.workload_uri", uris.ConnectWorkload);
                config[EDGED].document.ReplaceOrAdd("listen.management_uri", uris.ListenManagement);
                config[EDGED].document.ReplaceOrAdd("listen.workload_uri", uris.ListenWorkload);
            }
            else
            {
                UriSocks socks = this.uriSocks;
                config[EDGED].document.ReplaceOrAdd("connect.management_uri", socks.ConnectManagement);
                config[EDGED].document.ReplaceOrAdd("connect.workload_uri", socks.ConnectWorkload);
                config[EDGED].document.ReplaceOrAdd("listen.management_uri", socks.ListenManagement);
                config[EDGED].document.ReplaceOrAdd("listen.workload_uri", socks.ListenWorkload);
            }

            // Clear any existing Identity Service principals.
            string principalsPath = "/etc/aziot/identityd/config.d";

            config[IDENTITYD].document.RemoveIfExists("principal");
            if (Directory.Exists(principalsPath))
            {
                Directory.Delete(principalsPath, true);
            }

            Directory.CreateDirectory(principalsPath);
            SetOwner(principalsPath, "aziotid", "755");

            // Add the principal entry for aziot-edge to Identity Service.
            // This is required so aziot-edge can communicate with Identity Service.
            uint iotedgeUid = await GetIotedgeUid();

            AddPrincipal("aziot-edge", iotedgeUid);

            foreach (string file in new string[] { deviceCaCert, deviceCaPk, deviceCaCerts })
            {
                if (string.IsNullOrEmpty(file))
                {
                    throw new ArgumentException("device_ca_cert, device_ca_pk, and trusted_ca_certs must all be provided.");
                }

                if (!File.Exists(file))
                {
                    throw new ArgumentException($"{file} does not exist.");
                }
            }

            // Files must be readable by KS and CS users.
            SetOwner(deviceCaCerts, config[CERTD].owner, "444");
            SetOwner(deviceCaCert, config[CERTD].owner, "444");
            SetOwner(deviceCaPk, config[KEYD].owner, "400");

            config[CERTD].document.ReplaceOrAdd("preloaded_certs.aziot-edged-trust-bundle", new Uri(deviceCaCerts).AbsoluteUri);
            config[CERTD].document.ReplaceOrAdd("preloaded_certs.aziot-edged-ca", new Uri(deviceCaCert).AbsoluteUri);
            config[KEYD].document.ReplaceOrAdd("preloaded_keys.aziot-edged-ca", new Uri(deviceCaPk).AbsoluteUri);

            this.proxy.ForEach(proxy => config[EDGED].document.ReplaceOrAdd("agent.env.https_proxy", proxy));

            this.upstreamProtocol.ForEach(upstreamProtocol => config[EDGED].document.ReplaceOrAdd("agent.env.UpstreamProtocol", upstreamProtocol.ToString()));

            foreach (KeyValuePair <string, (string owner, IConfigDocument document)> service in config)
            {
                string path = service.Key;
                string text = service.Value.document.ToString();

                await File.WriteAllTextAsync(path, text);

                SetOwner(path, service.Value.owner, "644");
                Console.WriteLine($"Created config {path}");
            }
        }
Example #5
0
        public async Task Configure(DeviceProvisioningMethod method, Option <string> agentImage, string hostname, Option <string> parentHostname, string deviceCaCert, string deviceCaPk, string deviceCaCerts, LogLevel runtimeLogLevel)
        {
            // IMPORTANT: Parent hostname is not going to handled in Windows since Windows is not going to support for 1.1.0.
            const string HidePowerShellProgressBar = "$ProgressPreference='SilentlyContinue'";

            agentImage.ForEach(
                image =>
            {
                Console.WriteLine($"Setting up iotedged with agent image {image}");
            },
                () =>
            {
                Console.WriteLine("Setting up iotedged with agent image 1.0");
            });

            using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)))
            {
                if (!string.IsNullOrEmpty(this.offlineInstallationPath))
                {
                    this.scriptDir = File.GetAttributes(this.offlineInstallationPath).HasFlag(FileAttributes.Directory)
                        ? this.offlineInstallationPath
                        : new FileInfo(this.offlineInstallationPath).DirectoryName;
                }
                else
                {
                    this.scriptDir = Path.GetTempPath();
                    await Process.RunAsync(
                        "powershell",
                        $"{HidePowerShellProgressBar}; Invoke-WebRequest -UseBasicParsing -OutFile '{this.scriptDir}\\IotEdgeSecurityDaemon.ps1' aka.ms/iotedge-win",
                        cts.Token);
                }

                string args;
                if (this.requireEdgeInstallation)
                {
                    Console.WriteLine("Installing iotedge...");
                    args = $". {this.scriptDir}\\IotEdgeSecurityDaemon.ps1; Install-SecurityDaemon " + $"-ContainerOs Windows";

                    this.proxy.ForEach(proxy => { args += $" -Proxy '{proxy}'"; });

                    if (!string.IsNullOrEmpty(this.offlineInstallationPath))
                    {
                        args += $" -OfflineInstallationPath '{this.offlineInstallationPath}'";
                    }
                }
                else
                {
                    Console.WriteLine("Initializing iotedge...");
                    args = $". {this.scriptDir}\\IotEdgeSecurityDaemon.ps1; Initialize-IoTEdge " +
                           $"-ContainerOs Windows";
                }

                agentImage.ForEach(image =>
                {
                    args += $" -AgentImage '{image}'";
                    foreach (RegistryCredentials c in this.credentials)
                    {
                        args += $" -Username '{c.User}' -Password (ConvertTo-SecureString '{c.Password}' -AsPlainText -Force)";
                    }
                });

                args += method.Dps.Map(
                    dps =>
                {
                    string dpsArgs = string.Empty;
                    dpsArgs       += $" -Dps -ScopeId '{dps.ScopeId}'";
                    dps.RegistrationId.ForEach(id => { dpsArgs += $" -RegistrationId '{id}'"; });
                    dps.DeviceIdentityCertificate.ForEach(certPath => { dpsArgs += $" -X509IdentityCertificate '{certPath}'"; });
                    dps.DeviceIdentityPrivateKey.ForEach(pkPath => { dpsArgs += $" -X509IdentityPrivateKey '{pkPath}'"; });
                    dps.SymmetricKey.ForEach(symmKey => { dpsArgs += $" -SymmetricKey '{symmKey}'"; });
                    return(dpsArgs);
                }).GetOrElse(string.Empty);

                // ***************************************************************
                // IMPORTANT: All secret/sensitive argument should be place below.
                // ***************************************************************
                Console.WriteLine($"Run command arguments to configure: {args}");

                args += method.ManualConnectionString.Map(
                    cs => { return($" -Manual -DeviceConnectionString '{cs}'"); }).GetOrElse(string.Empty);

                // note: ignore hostname for now
                string[] result = await Process.RunAsync("powershell", $"{HidePowerShellProgressBar}; {args}", cts.Token);

                WriteToConsole("Output from Configure iotedge windows service", result);

                // Stop service and update config file
                await Task.Delay(TimeSpan.FromSeconds(5));

                await this.Stop();

                this.UpdateConfigYamlFile(deviceCaCert, deviceCaPk, deviceCaCerts, runtimeLogLevel);

                // Explicitly set IOTEDGE_HOST environment variable to current process
                this.SetEnvironmentVariable();
            }
        }
        public async Task Configure(DeviceProvisioningMethod method, Option <string> agentImage, string hostname, string deviceCaCert, string deviceCaPk, string deviceCaCerts, LogLevel runtimeLogLevel)
        {
            agentImage.ForEach(
                image =>
            {
                Console.WriteLine($"Setting up iotedged with agent image {image}");
            },
                () =>
            {
                Console.WriteLine("Setting up iotedged with agent image 1.0");
            });

            const string  YamlPath = "/etc/iotedge/config.yaml";
            Task <string> text     = File.ReadAllTextAsync(YamlPath);
            var           doc      = new YamlDocument(await text);

            method.ManualConnectionString.Match(
                cs =>
            {
                doc.ReplaceOrAdd("provisioning.device_connection_string", cs);
                return(string.Empty);
            },
                () =>
            {
                doc.Remove("provisioning.device_connection_string");
                return(string.Empty);
            });

            method.Dps.ForEach(
                dps =>
            {
                doc.ReplaceOrAdd("provisioning.source", "dps");
                doc.ReplaceOrAdd("provisioning.global_endpoint", dps.EndPoint);
                doc.ReplaceOrAdd("provisioning.scope_id", dps.ScopeId);
                switch (dps.AttestationType)
                {
                case DPSAttestationType.SymmetricKey:
                    doc.ReplaceOrAdd("provisioning.attestation.method", "symmetric_key");
                    doc.ReplaceOrAdd("provisioning.attestation.symmetric_key", dps.SymmetricKey.Expect(() => new ArgumentException("Expected symmetric key")));
                    break;

                case DPSAttestationType.X509:
                    var certUri = new Uri(dps.DeviceIdentityCertificate.Expect(() => new ArgumentException("Expected path to identity certificate")));
                    var keyUri  = new Uri(dps.DeviceIdentityPrivateKey.Expect(() => new ArgumentException("Expected path to identity private key")));
                    doc.ReplaceOrAdd("provisioning.attestation.method", "x509");
                    doc.ReplaceOrAdd("provisioning.attestation.identity_cert", certUri.AbsoluteUri);
                    doc.ReplaceOrAdd("provisioning.attestation.identity_pk", keyUri.AbsoluteUri);
                    break;

                default:
                    doc.ReplaceOrAdd("provisioning.attestation.method", "tpm");
                    break;
                }

                dps.RegistrationId.ForEach(id => { doc.ReplaceOrAdd("provisioning.attestation.registration_id", id); });
            });

            agentImage.ForEach(image =>
            {
                doc.ReplaceOrAdd("agent.config.image", image);
            });

            doc.ReplaceOrAdd("hostname", hostname);

            foreach (RegistryCredentials c in this.credentials)
            {
                doc.ReplaceOrAdd("agent.config.auth.serveraddress", c.Address);
                doc.ReplaceOrAdd("agent.config.auth.username", c.User);
                doc.ReplaceOrAdd("agent.config.auth.password", c.Password);
            }

            doc.ReplaceOrAdd("agent.env.RuntimeLogLevel", runtimeLogLevel.ToString());

            if (this.httpUris.HasValue)
            {
                HttpUris uris = this.httpUris.OrDefault();
                doc.ReplaceOrAdd("connect.management_uri", uris.ConnectManagement);
                doc.ReplaceOrAdd("connect.workload_uri", uris.ConnectWorkload);
                doc.ReplaceOrAdd("listen.management_uri", uris.ListenManagement);
                doc.ReplaceOrAdd("listen.workload_uri", uris.ListenWorkload);
            }
            else
            {
                doc.ReplaceOrAdd("connect.management_uri", "unix:///var/run/iotedge/mgmt.sock");
                doc.ReplaceOrAdd("connect.workload_uri", "unix:///var/run/iotedge/workload.sock");
                doc.ReplaceOrAdd("listen.management_uri", "fd://iotedge.mgmt.socket");
                doc.ReplaceOrAdd("listen.workload_uri", "fd://iotedge.socket");
            }

            if (!string.IsNullOrEmpty(deviceCaCert) && !string.IsNullOrEmpty(deviceCaPk) && !string.IsNullOrEmpty(deviceCaCerts))
            {
                doc.ReplaceOrAdd("certificates.device_ca_cert", deviceCaCert);
                doc.ReplaceOrAdd("certificates.device_ca_pk", deviceCaPk);
                doc.ReplaceOrAdd("certificates.trusted_ca_certs", deviceCaCerts);
            }

            this.proxy.ForEach(proxy => doc.ReplaceOrAdd("agent.env.https_proxy", proxy));

            this.upstreamProtocol.ForEach(upstreamProtocol => doc.ReplaceOrAdd("agent.env.UpstreamProtocol", upstreamProtocol.ToString()));

            string result = doc.ToString();

            FileAttributes attr = 0;

            if (File.Exists(YamlPath))
            {
                attr = File.GetAttributes(YamlPath);
                File.SetAttributes(YamlPath, attr & ~FileAttributes.ReadOnly);
            }

            await File.WriteAllTextAsync(YamlPath, result);

            if (attr != 0)
            {
                File.SetAttributes(YamlPath, attr);
            }
        }
Example #7
0
        public async Task Configure(
            DeviceProvisioningMethod method,
            Option <string> agentImage,
            string hostname,
            Option <string> parentHostname,
            string deviceCaCert,
            string deviceCaPk,
            string deviceCaCerts,
            LogLevel runtimeLogLevel)
        {
            agentImage.ForEach(
                image =>
            {
                Console.WriteLine($"Setting up aziot-edged with agent image {image}");
            },
                () =>
            {
                Console.WriteLine("Setting up aziot-edged with agent image 1.0");
            });

            // Initialize each service's config file.
            Dictionary <string, Config> config = new Dictionary <string, Config>();

            config.Add(KEYD, await InitConfig(KEYD + ".default", "aziotks"));
            config.Add(CERTD, await InitConfig(CERTD + ".default", "aziotcs"));
            config.Add(IDENTITYD, await InitConfig(IDENTITYD + ".default", "aziotid"));
            config.Add(EDGED, await InitConfig(EDGED + ".default", "iotedge"));

            // Directory for storing keys; create it if it doesn't exist.
            string keyDir = "/var/secrets/aziot/keyd/";

            Directory.CreateDirectory(keyDir);
            SetOwner(keyDir, config[KEYD].Owner, "700");

            // Need to always reprovision so previous test runs don't affect this one.
            config[EDGED].Document.ReplaceOrAdd("auto_reprovisioning_mode", "AlwaysOnStartup");
            config[IDENTITYD].Document.RemoveIfExists("provisioning");
            parentHostname.ForEach(
                parent_hostame =>
                config[IDENTITYD].Document.ReplaceOrAdd("provisioning.local_gateway_hostname", parent_hostame));

            method.ManualConnectionString.Match(
                cs =>
            {
                string keyPath = Path.Combine(keyDir, "device-id");
                config[IDENTITYD].Document.ReplaceOrAdd("provisioning.source", "manual");
                config[IDENTITYD].Document.ReplaceOrAdd("provisioning.authentication.method", "sas");
                config[IDENTITYD].Document.ReplaceOrAdd("provisioning.authentication.device_id_pk", "device-id");
                config[KEYD].Document.ReplaceOrAdd("preloaded_keys.device-id", $"file://{keyPath}");

                string[] segments = cs.Split(";");

                foreach (string s in segments)
                {
                    string[] param = s.Split("=", 2);

                    switch (param[0])
                    {
                    case "HostName":
                        // replace IoTHub hostname with parent hostname for nested edge
                        config[IDENTITYD].Document.ReplaceOrAdd("provisioning.iothub_hostname", param[1]);
                        break;

                    case "SharedAccessKey":
                        File.WriteAllBytes(keyPath, Convert.FromBase64String(param[1]));
                        SetOwner(keyPath, config[KEYD].Owner, "600");
                        break;

                    case "DeviceId":
                        config[IDENTITYD].Document.ReplaceOrAdd("provisioning.device_id", param[1]);
                        break;

                    default:
                        break;
                    }
                }

                this.SetAuth("device-id", config);

                return(string.Empty);
            },
                () =>
            {
                config[IDENTITYD].Document.RemoveIfExists("provisioning");
                return(string.Empty);
            });

            method.Dps.ForEach(
                dps =>
            {
                config[IDENTITYD].Document.ReplaceOrAdd("provisioning.source", "dps");
                config[IDENTITYD].Document.ReplaceOrAdd("provisioning.global_endpoint", dps.EndPoint);
                config[IDENTITYD].Document.ReplaceOrAdd("provisioning.scope_id", dps.ScopeId);
                switch (dps.AttestationType)
                {
                case DPSAttestationType.SymmetricKey:
                    string dpsKeyPath = Path.Combine(keyDir, "device-id");
                    string dpsKey     = dps.SymmetricKey.Expect(() => new ArgumentException("Expected symmetric key"));

                    File.WriteAllBytes(dpsKeyPath, Convert.FromBase64String(dpsKey));
                    SetOwner(dpsKeyPath, config[KEYD].Owner, "600");

                    config[KEYD].Document.ReplaceOrAdd("preloaded_keys.device-id", new Uri(dpsKeyPath).AbsoluteUri);
                    config[IDENTITYD].Document.ReplaceOrAdd("provisioning.attestation.method", "symmetric_key");
                    config[IDENTITYD].Document.ReplaceOrAdd("provisioning.attestation.symmetric_key", "device-id");

                    this.SetAuth("device-id", config);

                    break;

                case DPSAttestationType.X509:
                    string certPath = dps.DeviceIdentityCertificate.Expect(() => new ArgumentException("Expected path to identity certificate"));
                    string keyPath  = dps.DeviceIdentityPrivateKey.Expect(() => new ArgumentException("Expected path to identity private key"));

                    SetOwner(certPath, config[CERTD].Owner, "444");
                    SetOwner(keyPath, config[KEYD].Owner, "400");

                    config[CERTD].Document.ReplaceOrAdd("preloaded_certs.device-id", new Uri(certPath).AbsoluteUri);
                    config[KEYD].Document.ReplaceOrAdd("preloaded_keys.device-id", new Uri(keyPath).AbsoluteUri);

                    config[IDENTITYD].Document.ReplaceOrAdd("provisioning.attestation.method", "x509");
                    config[IDENTITYD].Document.ReplaceOrAdd("provisioning.attestation.identity_cert", "device-id");
                    config[IDENTITYD].Document.ReplaceOrAdd("provisioning.attestation.identity_pk", "device-id");

                    this.SetAuth("device-id", config);

                    break;

                default:
                    break;
                }

                dps.RegistrationId.ForEach(id => { config[IDENTITYD].Document.ReplaceOrAdd("provisioning.attestation.registration_id", id); });
            });

            agentImage.ForEach(image =>
            {
                config[EDGED].Document.ReplaceOrAdd("agent.config.image", image);
            });

            config[EDGED].Document.ReplaceOrAdd("hostname", hostname);
            config[IDENTITYD].Document.ReplaceOrAdd("hostname", hostname);

            parentHostname.ForEach(v => config[EDGED].Document.ReplaceOrAdd("parent_hostname", v));

            foreach (RegistryCredentials c in this.credentials)
            {
                config[EDGED].Document.ReplaceOrAdd("agent.config.auth.serveraddress", c.Address);
                config[EDGED].Document.ReplaceOrAdd("agent.config.auth.username", c.User);
                config[EDGED].Document.ReplaceOrAdd("agent.config.auth.password", c.Password);
            }

            config[EDGED].Document.ReplaceOrAdd("agent.env.RuntimeLogLevel", runtimeLogLevel.ToString());

            if (this.httpUris.HasValue)
            {
                HttpUris uris = this.httpUris.OrDefault();
                config[EDGED].Document.ReplaceOrAdd("connect.management_uri", uris.ConnectManagement);
                config[EDGED].Document.ReplaceOrAdd("connect.workload_uri", uris.ConnectWorkload);
                config[EDGED].Document.ReplaceOrAdd("listen.management_uri", uris.ListenManagement);
                config[EDGED].Document.ReplaceOrAdd("listen.workload_uri", uris.ListenWorkload);
            }
            else
            {
                UriSocks socks = this.uriSocks;
                config[EDGED].Document.ReplaceOrAdd("connect.management_uri", socks.ConnectManagement);
                config[EDGED].Document.ReplaceOrAdd("connect.workload_uri", socks.ConnectWorkload);
                config[EDGED].Document.ReplaceOrAdd("listen.management_uri", socks.ListenManagement);
                config[EDGED].Document.ReplaceOrAdd("listen.workload_uri", socks.ListenWorkload);
            }

            foreach (string file in new string[] { deviceCaCert, deviceCaPk, deviceCaCerts })
            {
                if (string.IsNullOrEmpty(file))
                {
                    throw new ArgumentException("device_ca_cert, device_ca_pk, and trusted_ca_certs must all be provided.");
                }

                if (!File.Exists(file))
                {
                    throw new ArgumentException($"{file} does not exist.");
                }
            }

            // Files must be readable by KS and CS users.
            SetOwner(deviceCaCerts, config[CERTD].Owner, "444");
            SetOwner(deviceCaCert, config[CERTD].Owner, "444");
            SetOwner(deviceCaPk, config[KEYD].Owner, "400");

            config[CERTD].Document.ReplaceOrAdd("preloaded_certs.aziot-edged-trust-bundle", new Uri(deviceCaCerts).AbsoluteUri);
            config[CERTD].Document.ReplaceOrAdd("preloaded_certs.aziot-edged-ca", new Uri(deviceCaCert).AbsoluteUri);
            config[KEYD].Document.ReplaceOrAdd("preloaded_keys.aziot-edged-ca", new Uri(deviceCaPk).AbsoluteUri);

            this.proxy.ForEach(proxy => config[EDGED].Document.ReplaceOrAdd("agent.env.https_proxy", proxy));

            this.upstreamProtocol.ForEach(upstreamProtocol => config[EDGED].Document.ReplaceOrAdd("agent.env.UpstreamProtocol", upstreamProtocol.ToString()));

            foreach (KeyValuePair <string, Config> service in config)
            {
                string path = service.Key;
                string text = service.Value.Document.ToString();

                await File.WriteAllTextAsync(path, text);

                SetOwner(path, service.Value.Owner, "644");
                Console.WriteLine($"Created config {path}");
            }

            using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)))
            {
                Console.WriteLine($"Calling iotedge system set-log-level {runtimeLogLevel.ToString().ToLower()}");
                string[] output = await Process.RunAsync("iotedge", $"system set-log-level {runtimeLogLevel.ToString().ToLower()}", cts.Token);

                Console.WriteLine($"{output.ToString()}");
            }
        }