CartItems { get; set; }
9 | public decimal CartTotal { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/ConfirmEmail.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Confirm Email";
3 | }
4 |
5 | @ViewBag.Title.
6 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/ExternalLoginConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @model ExternalLoginConfirmationViewModel
2 | @{
3 | ViewBag.Title = "Register";
4 | }
5 | @ViewBag.Title.
6 | Associate your @ViewBag.LoginProvider account.
7 |
8 |
31 |
32 | @section Scripts {
33 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
34 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/ExternalLoginFailure.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Login Failure";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 | Unsuccessful login with service.
8 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/ForgotPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model ForgotPasswordViewModel
2 | @{
3 | ViewBag.Title = "Forgot your password?";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 |
25 |
26 | @section Scripts {
27 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
28 | }
29 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/ForgotPasswordConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Forgot Password Confirmation";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 |
8 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/Login.cshtml:
--------------------------------------------------------------------------------
1 | @model LoginViewModel
2 |
3 | @{
4 | ViewBag.Title = "Log in";
5 | }
6 |
7 | @ViewBag.Title.
8 |
57 |
58 | @section Scripts {
59 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
60 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/Register.cshtml:
--------------------------------------------------------------------------------
1 | @model RegisterViewModel
2 | @{
3 | ViewBag.Title = "Register";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 |
39 |
40 | @section Scripts {
41 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
42 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/RegisterConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Register Confirmation";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 |
8 |
9 |
10 | Please check your email to activate your account.
11 |
12 |
13 | Demo/testing purposes only: The sample displays the code and user id in the page: Click here to confirm your email:
14 |
15 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/ResetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model ResetPasswordViewModel
2 | @{
3 | ViewBag.Title = "Reset password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 |
40 |
41 | @section Scripts {
42 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
43 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/ResetPasswordConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Reset password confirmation";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 |
8 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/SendCode.cshtml:
--------------------------------------------------------------------------------
1 | @model SendCodeViewModel
2 | @{
3 | ViewBag.Title = "Send Verification Code";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 |
18 |
19 | @section Scripts {
20 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
21 | }
22 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/VerifyCode.cshtml:
--------------------------------------------------------------------------------
1 | @model VerifyCodeViewModel
2 | @{
3 | ViewBag.Title = "Verify";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 |
40 |
41 | @section Scripts {
42 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
43 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Account/_ExternalLoginsListPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Authentication
2 | @model ExternalLoginListViewModel
3 | @inject IAuthenticationSchemeProvider SchemeProvider
4 | Use another service to log in.
5 |
6 | @{
7 | var schemes = await SchemeProvider.GetAllSchemesAsync();
8 | var loginProviders = schemes.ToList();
9 | if (!loginProviders.Any())
10 | {
11 |
12 |
13 | There are no external authentication services configured. See this article
14 | for details on setting up this ASP.NET application to support logging in via external services.
15 |
16 |
17 | }
18 | else
19 | {
20 |
30 | }
31 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Checkout/AddressAndPayment.cshtml:
--------------------------------------------------------------------------------
1 | @model Order
2 |
3 | @{
4 | ViewBag.Title = "Address And Payment";
5 | }
6 |
7 | @section Scripts {
8 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Checkout/Complete.cshtml:
--------------------------------------------------------------------------------
1 | @model int
2 |
3 | @{
4 | ViewBag.Title = "Checkout Complete";
5 | }
6 |
7 | Checkout Complete
8 |
9 | Thanks for your order! Your order number is: @Model
10 |
11 |
12 | How about shopping for some more unicorns in our
13 | Store
14 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.Extensions.Configuration
2 | @inject IConfiguration Configuration
3 | @{
4 | ViewBag.Title = "Home Page";
5 | }
6 |
7 |
8 |
@Configuration["AppSettings:SiteTitle"]
9 |

10 |
11 |
12 |
23 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Manage/AddPhoneNumber.cshtml:
--------------------------------------------------------------------------------
1 | @model AddPhoneNumberViewModel
2 | @{
3 | ViewBag.Title = "Add Phone Number";
4 | }
5 |
6 | @ViewBag.Title.
7 |
24 |
25 | @section Scripts {
26 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
27 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Manage/ChangePassword.cshtml:
--------------------------------------------------------------------------------
1 | @model ChangePasswordViewModel
2 | @{
3 | ViewBag.Title = "Change Password";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 |
39 |
40 | @section Scripts {
41 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
42 | }
43 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Manage/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model IndexViewModel
2 | @{
3 | ViewData["Title"] = "Manage your account";
4 | }
5 |
6 | @ViewData["Title"].
7 | @ViewData["StatusMessage"]
8 |
9 |
10 |
Change your account settings
11 |
12 |
13 | - Password:
14 | -
15 | @if (Model.HasPassword)
16 | {
17 | [ Change ]
18 | }
19 | else
20 | {
21 | [ Create ]
22 | }
23 |
24 | - External Logins:
25 | -
26 | @Model.Logins.Count [ Manage ]
27 |
28 | - Phone Number:
29 | -
30 |
31 | Phone Numbers can used as a second factor of verification in two-factor authentication.
32 | See this article
33 | for details on setting up this ASP.NET application to support two-factor authentication using SMS.
34 |
35 | @*@(Model.PhoneNumber ?? "None")
36 | @if (Model.PhoneNumber != null)
37 | {
38 |
39 | [ Change ]
40 |
43 | }
44 | else
45 | {
46 | [ Add ]
47 | }*@
48 |
49 |
50 | - Two-Factor Authentication:
51 | -
52 |
53 | There are no two-factor authentication providers configured. See this article
54 | for setting up this application to support two-factor authentication.
55 |
56 | @*@if (Model.TwoFactor)
57 | {
58 |
61 | }
62 | else
63 | {
64 |
67 | }*@
68 |
69 |
70 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Manage/ManageLogins.cshtml:
--------------------------------------------------------------------------------
1 | @model ManageLoginsViewModel
2 | @{
3 | ViewBag.Title = "Manage your external logins";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 | @ViewBag.StatusMessage
9 | @if (Model.CurrentLogins.Count > 0)
10 | {
11 | Registered Logins
12 |
13 |
14 | @foreach (var account in Model.CurrentLogins)
15 | {
16 |
17 | @account.LoginProvider |
18 |
19 | @if (ViewBag.ShowRemoveButton)
20 | {
21 |
28 | }
29 | else
30 | {
31 | @:
32 | }
33 | |
34 |
35 | }
36 |
37 |
38 | }
39 | @if (Model.OtherLogins.Any())
40 | {
41 | Add another service to log in.
42 |
43 |
53 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Manage/SetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @model SetPasswordViewModel
2 | @{
3 | ViewBag.Title = "Set Password";
4 | }
5 |
6 |
7 | You do not have a local username/password for this site. Add a local
8 | account so you can log in without an external login.
9 |
10 |
11 |
35 |
36 | @section Scripts {
37 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
38 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Manage/VerifyPhoneNumber.cshtml:
--------------------------------------------------------------------------------
1 | @model VerifyPhoneNumberViewModel
2 | @{
3 | ViewBag.Title = "Verify Phone Number";
4 | }
5 |
6 | @ViewBag.Title.
7 |
8 |
31 |
32 | @section Scripts {
33 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
34 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/AccessDenied.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Access denied due to insufficient permissions";
3 | }
4 |
5 | Access denied due to insufficient permissions.
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/Components/Announcement/Default.cshtml:
--------------------------------------------------------------------------------
1 | @model Blessing
2 |
3 | @if (Model != null)
4 | {
5 |
6 |
7 |
8 | @Model.Title
9 |
10 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/Components/CartSummary/Default.cshtml:
--------------------------------------------------------------------------------
1 | @if (ViewBag.CartCount > 0)
2 | {
3 |
4 |
5 |
6 |
7 | @ViewBag.CartCount
8 |
9 |
10 |
11 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/Components/GenreMenu/Default.cshtml:
--------------------------------------------------------------------------------
1 | @model IEnumerable
2 |
3 |
4 | Store
5 |
17 |
18 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/DemoLinkDisplay.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Demo link display page - Not for production use";
3 | }
4 |
5 |
6 | @ViewBag.Title.
7 |
8 |
9 |
10 | Demo link display page - Not for production use.
11 |
12 |
13 | @if (ViewBag.Link != null)
14 | {
15 |
16 | For DEMO only: You can click this link to confirm the email: [[link]]
17 |
18 | Please change this code to register an email service in IdentityConfig to send an email.
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Error";
3 | }
4 |
5 | Error.
6 | An error occurred while processing your request.
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/Lockout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Locked Out";
3 | }
4 |
5 |
6 | Locked out.
7 | This account has been locked out, please try again later.
8 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/StatusCodePage.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Item not found";
3 | }
4 |
5 | Item not found.
6 | Unable to find the item you are searching for. Please try again.
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.Extensions.Configuration
2 | @inject IConfiguration Configuration
3 |
4 |
5 |
6 |
7 |
8 | @ViewBag.Title - @Configuration["AppSettings:SiteTitle"]
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
39 |
40 |
41 | - Home
42 | @await Component.InvokeAsync("GenreMenu")
43 | @await Component.InvokeAsync("CartSummary")
44 |
45 | @await Html.PartialAsync("_LoginPartial")
46 |
47 |
48 |
49 |
50 | @RenderBody()
51 |
52 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
70 |
71 |
72 | @RenderSection("scripts", required: false)
73 |
74 |
75 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/_LoginPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Identity
2 | @using UnicornStore.Models
3 |
4 | @inject SignInManager SignInManager
5 | @inject UserManager UserManager
6 |
7 | @if (SignInManager.IsSignedIn(User))
8 | {
9 |
17 | }
18 | else if (User.Identity.IsAuthenticated)
19 | {
20 | //This code block necessary only for NTLM authentication
21 |
26 | }
27 | else
28 | {
29 |
33 | }
--------------------------------------------------------------------------------
/UnicornStore/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
14 |
--------------------------------------------------------------------------------
/UnicornStore/Views/ShoppingCart/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model UnicornStore.ViewModels.ShoppingCartViewModel
2 | @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
3 | @{
4 | ViewBag.Title = "Shopping Cart";
5 | }
6 |
7 | @functions
8 | {
9 | public string GetAntiXsrfRequestToken()
10 | {
11 | return Xsrf.GetAndStoreTokens(Context).RequestToken;
12 | }
13 | }
14 |
15 | @section Scripts {
16 |
53 | }
54 |
55 |
56 | Review your cart:
57 |
58 |
59 | Checkout >>
60 |
61 |
62 |
63 |
64 |
65 |
66 | Blessing Name
67 | |
68 |
69 | Price (each)
70 | |
71 |
72 | Quantity
73 | |
74 | |
75 |
76 | @foreach (var item in Model.CartItems)
77 | {
78 |
79 |
80 | @item.Blessing.Title
81 | |
82 |
83 | @item.Blessing.Price
84 | |
85 |
86 | @item.Count
87 | |
88 |
89 |
91 | Remove from cart
92 |
93 | |
94 |
95 | }
96 |
97 |
98 | Total
99 | |
100 | |
101 | |
102 |
103 | @Model.CartTotal
104 | |
105 |
106 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Store/Browse.cshtml:
--------------------------------------------------------------------------------
1 | @model Genre
2 | @{
3 | ViewBag.Title = "Unicorns";
4 | }
5 |
6 |
7 | @Model.Name Unicorns
8 |
9 |
10 |
24 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Store/Details.cshtml:
--------------------------------------------------------------------------------
1 | @model Blessing
2 |
3 | @{
4 | ViewBag.Title = "Blessing - " + Model.Title;
5 | }
6 |
7 | @Model.Title
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Genre:
16 | @Model.Genre.Name
17 |
18 |
19 | Unicorn:
20 | @Model.Unicorn.Name
21 |
22 |
23 | Price:
24 |
25 |
26 |
27 | Add to cart
28 |
29 |
--------------------------------------------------------------------------------
/UnicornStore/Views/Store/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model IEnumerable
2 | @{
3 | ViewBag.Title = "Store";
4 | }
5 | Browse Unicorns
6 |
7 |
8 | Select from @Model.Count() Unicorn genres:
9 |
10 |
11 | @foreach (var genre in Model)
12 | {
13 | - @genre.Name
14 | }
15 |
--------------------------------------------------------------------------------
/UnicornStore/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using UnicornStore
2 | @using UnicornStore.Models
3 | @using Microsoft.Extensions.Options
4 | @using Microsoft.AspNetCore.Identity
5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
6 |
--------------------------------------------------------------------------------
/UnicornStore/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "/Views/Shared/_Layout.cshtml";
3 | }
--------------------------------------------------------------------------------
/UnicornStore/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSettings": {
3 | "SiteTitle": "Modernization Unicorn Store Dev",
4 | "CacheDbResults": false
5 | },
6 | "DefaultAdminUsername": "",
7 | "DefaultAdminPassword": "",
8 | "ConnectionStrings": {
9 | "UnicornStore": "Database=UnicornStore;Trusted_Connection=False;MultipleActiveResultSets=true;Connect Timeout=30;"
10 | },
11 | "Logging": {
12 | "LogLevel": {
13 | "Default": "Debug",
14 | "System": "Information",
15 | "Microsoft": "Information"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/UnicornStore/appsettings.Production.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSettings": {
3 | "SiteTitle": "Modernization Unicorn Store Prod",
4 | "CacheDbResults": false
5 | },
6 | "DefaultAdminUsername": "",
7 | "DefaultAdminPassword": "",
8 | "ConnectionStrings": {
9 | "UnicornStore": "Database=UnicornStoreProd;Trusted_Connection=False;MultipleActiveResultSets=true;Connect Timeout=30;"
10 | },
11 | "Logging": {
12 | "LogLevel": {
13 | "Default": "Information",
14 | "System": "Information",
15 | "Microsoft": "Information"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/UnicornStore/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/Content/Site.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 50px;
3 | padding-bottom: 20px;
4 | }
5 |
6 | /* Set padding to keep content from hitting the edges */
7 | .body-content {
8 | padding-left: 15px;
9 | padding-right: 15px;
10 | }
11 |
12 | /* Set width on the form input elements since they're 100% wide by default */
13 | input,
14 | select,
15 | textarea {
16 | max-width: 280px;
17 | }
18 |
19 | /* styles for validation helpers */
20 | .field-validation-error {
21 | color: #b94a48;
22 | }
23 |
24 | .field-validation-valid {
25 | display: none;
26 | }
27 |
28 | input.input-validation-error {
29 | border: 1px solid #b94a48;
30 | }
31 |
32 | input[type="checkbox"].input-validation-error {
33 | border: 0 none;
34 | }
35 |
36 | .validation-summary-errors {
37 | color: #b94a48;
38 | }
39 |
40 | .validation-summary-valid {
41 | display: none;
42 | }
43 |
44 |
45 | /* Unicorn Store additions */
46 |
47 | ul#blessing-list li {
48 | height: 160px;
49 | }
50 |
51 | ul#blessing-list li img:hover {
52 | box-shadow: 1px 1px 7px #777;
53 | }
54 |
55 | ul#blessing-list li img {
56 | box-shadow: 1px 1px 5px #999;
57 | border: none;
58 | padding: 0;
59 | }
60 |
61 | ul#blessing-list li a, ul#blessing-details li a {
62 | text-decoration:none;
63 | }
64 |
65 | ul#blessing-list li a:hover {
66 | background: none;
67 | -webkit-text-shadow: 1px 1px 2px #bbb;
68 | text-shadow: 1px 1px 2px #bbb;
69 | color: #363430;
70 | }
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/Images/main-unicorn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/UnicornStore/wwwroot/Images/main-unicorn.png
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/Images/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/UnicornStore/wwwroot/Images/placeholder.png
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/Images/unicorn-two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/UnicornStore/wwwroot/Images/unicorn-two.png
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/Scripts/jquery.validate.unobtrusive.min.js:
--------------------------------------------------------------------------------
1 | /* NUGET: BEGIN LICENSE TEXT
2 | *
3 | * Microsoft grants you the right to use these script files for the sole
4 | * purpose of either: (i) interacting through your browser with the Microsoft
5 | * website or online service, subject to the applicable licensing or use
6 | * terms; or (ii) using the files as included with a Microsoft product subject
7 | * to that product's license terms. Microsoft reserves all other rights to the
8 | * files not expressly granted by Microsoft, whether by implication, estoppel
9 | * or otherwise. Insofar as a script file is dual licensed under GPL,
10 | * Microsoft neither took the code under GPL nor distributes it thereunder but
11 | * under the terms set out in this paragraph. All notices and licenses
12 | * below are for informational purposes only.
13 | *
14 | * NUGET: END LICENSE TEXT */
15 | /*
16 | ** Unobtrusive validation support library for jQuery and jQuery Validate
17 | ** Copyright (C) Microsoft Corporation. All rights reserved.
18 | */
19 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this);b.data("validator").resetForm();b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(c){var b=a(c),d=b.data(e),f=a.proxy(n,c);if(!d){d={options:{errorClass:"input-validation-error",errorElement:"span",errorPlacement:a.proxy(m,c),invalidHandler:a.proxy(l,c),messages:{},rules:{},success:a.proxy(k,c)},attachValidation:function(){b.unbind("reset."+e,f).bind("reset."+e,f).validate(this.options)},validate:function(){b.validate();return b.valid()}};b.data(e,d)}return d}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(b){var c=a(b).parents("form").andSelf().add(a(b).find("form")).filter("form");a(b).find(":input").filter("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});c.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){return a(b.form).find(":input").filter("[name='"+f(c)+"']").val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery);
20 |
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/Scripts/respond.min.js:
--------------------------------------------------------------------------------
1 | /* NUGET: BEGIN LICENSE TEXT
2 | *
3 | * Microsoft grants you the right to use these script files for the sole
4 | * purpose of either: (i) interacting through your browser with the Microsoft
5 | * website or online service, subject to the applicable licensing or use
6 | * terms; or (ii) using the files as included with a Microsoft product subject
7 | * to that product's license terms. Microsoft reserves all other rights to the
8 | * files not expressly granted by Microsoft, whether by implication, estoppel
9 | * or otherwise. Insofar as a script file is dual licensed under GPL,
10 | * Microsoft neither took the code under GPL nor distributes it thereunder but
11 | * under the terms set out in this paragraph. All notices and licenses
12 | * below are for informational purposes only.
13 | *
14 | * NUGET: END LICENSE TEXT */
15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
17 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document);
18 |
19 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
20 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this);
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/UnicornStore/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/UnicornStore/wwwroot/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/UnicornStore/wwwroot/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/UnicornStore/wwwroot/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/UnicornStore/wwwroot/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/content/secrets/_index.md:
--------------------------------------------------------------------------------
1 | # Securing Your .NET Container Secrets
2 |
3 | As customers move .NET workloads to the cloud, many start to consider containerizing their applications because of the agility and cost savings that containers provide. Combine those compelling drivers with the multi-OS capabilities that come with .NET Core, and customers have an exciting reason to migrate their applications. A primary question is how they can safely store secrets and sensitive configuration values in containerized workloads. In this workshop, learn how to safely containerize the Unicorn Store.
4 |
5 | You will learn how to run the Unicorn Store which is an ASP.NET Core application in a Docker container while connecting to a SQL backend (Database=UnicornStore) in [Amazon RDS](https://aws.amazon.com/rds/). The RDS credentials to the database in are stored in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) along with other sensitive information needed for the application to run. This allows the Unicorn Store application to safely connect to the database from the container without storing the secrets in a file on the container or in source control.
6 |
7 | Take the time to read [mutiple environments in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-3.0) and [safe storage of app secrets in development in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.0&tabs=windows) before starting so you understand the various different configuration options.
8 |
9 | ## Architecture Overview
10 |
11 | 
12 |
13 | ## Getting Started
14 |
15 | Click [here](/content/secrets/prerequisites/_index.md) to start the workshop.
--------------------------------------------------------------------------------
/content/secrets/accessing-secrets.md:
--------------------------------------------------------------------------------
1 | # Accessing Secrets
2 |
3 | Now that you've set up the secret for local development, you may be wondering how can you access a secret in your .NET Core code. The [ASP.NET Core Configuration API](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/index?view=aspnetcore-3.0) provides access to Secret Manager secrets.
4 |
5 | In ASP.NET 2.0 or later, the user secrets configuration source is automatically added in development mode when the project calls [CreateDefaultBuilder](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.webhost.createdefaultbuilder) to initialize a new instance of the host with preconfigured defaults. Below is a snippet from our [Program.cs](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/Program.cs) file.
6 |
7 | 
8 |
9 | Anytime we want to retrieve a user secret during local development, we can do so via the Configuration API. Below is a snippet from our [Startup.cs](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/Startup.cs) file. You'll notice that IConfiguration is injected into the Startup constructor to access configuration values. Once that's done, accessing a key/value for something like the password value is as simple as calling ***Configuration["UNICORNSTORE_DBSECRET:password"]***.
10 |
11 | 
12 |
13 | You may have noticed in the above snippet of code that we are constructing our database connection string using [SqlConnectionStringBuilder](https://docs.microsoft.com/dotnet/api/system.data.sqlclient.sqlconnectionstringbuilder). This is best practice because now we aren't storing sensitive information like a password in plain text which is insecure. Look at the [appsettings.Development.json](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/appsettings.Development.json) file in our project. You'll notice that none of the sensitive information is in the connection string. Only a portion of what's needed is set there and the rest of the connection string can be driven by configuration based on environment.
14 |
15 | 
16 |
17 | The Configuration API is a very powerful feature of .NET Core and can handle multiple configuration sources. When an ASP.NET Core application starts, it loads your configuration providers in the order they are configured. If a configuration source is loaded and the key already exists, it overwrites the previous value meaning the last key loaded wins.
18 |
19 | In ***Program.cs*** there is a method called ***CreateDefaultBuilder*** which is behind the configuration provider setup. Looking at the [CreateDefaultBuilder](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.host.createdefaultbuilder?view=dotnet-plat-ext-3.0) method, we can see that the order of providers are configured as follows:
20 |
21 | 1. Files (appsettings.json, appsettings.{Environment}.json, where {Environment} is the app's current hosting environment)
22 | 2. User secrets (Secret Manager) (in the Development environment only)
23 | 3. Environment variables
24 | 4. Command-line arguments
25 |
26 | It's a common practice to position the Command-line Configuration Provider last in a series of providers to allow command-line arguments to override configuration set by the other providers.
27 |
28 | This sequence of providers is put into place when you initialize a new WebHostBuilder with CreateDefaultBuilder.
29 |
30 | Click [**here**](/content/secrets/create-secrets.md) to move to the next section where we will create some more local secrets for the Unicorn Store and run the application in your development environment.
--------------------------------------------------------------------------------
/content/secrets/cfn/player-template.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: "2010-09-09"
3 | Description: This template will launch the Player Account Environment for .Net Modernization Workshop.
4 | Parameters:
5 | BucketName:
6 | Type: String
7 | Description: "S3 Bucket Name where nested templates are Stored in"
8 | BucketPrefix:
9 | Type: String
10 | Description: S3 Prefix for nested template artifacts
11 | UnicornStoreDBUsername:
12 | Type: String
13 | Description: UnicornStore Database Username for RDS
14 | Default: "awssa"
15 | UnicornStoreDBPassword:
16 | AllowedPattern: "^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)"
17 | ConstraintDescription: Must contain only alphanumeric characters with at least one capital letter and one number
18 | Description: UnicornStore Database Username for RDS
19 | MaxLength: '41'
20 | MinLength: '8'
21 | Type: String
22 | Default: BBTh123ca
23 | Resources:
24 | BasicVPC:
25 | Type: "AWS::CloudFormation::Stack"
26 | Properties:
27 | TemplateURL: !Sub "https://${BucketName}.s3.amazonaws.com/${BucketPrefix}/templates/player-vpc-template.yaml"
28 | TimeoutInMinutes: 10
29 | WorkshopIDE:
30 | Type: "AWS::Cloud9::EnvironmentEC2"
31 | Properties:
32 | Description: "Cloud9 Browser Based IDE for executing the modernization AWS Workshop"
33 | AutomaticStopTimeMinutes: 60
34 | InstanceType: t3.small
35 | SubnetId: !GetAtt BasicVPC.Outputs.PublicSubnet1
36 | UnicornStoreRDSSecurityGroup:
37 | Type: "AWS::EC2::SecurityGroup"
38 | Properties:
39 | GroupDescription: UnicornStoreRDSSecurityGroup
40 | SecurityGroupIngress:
41 | - IpProtocol: tcp
42 | FromPort: 1433
43 | ToPort: 1433
44 | CidrIp: !GetAtt BasicVPC.Outputs.VPCCIDR
45 | VpcId: !GetAtt BasicVPC.Outputs.VPCId
46 | UnicornStoreRDS:
47 | Type: "AWS::RDS::DBInstance"
48 | Properties:
49 | AllocatedStorage: 20
50 | DBInstanceClass: db.t2.medium
51 | Port: 1433
52 | PubliclyAccessible: 'true'
53 | StorageType: gp2
54 | MasterUsername: !Ref UnicornStoreDBUsername
55 | MasterUserPassword: !Ref UnicornStoreDBPassword
56 | Engine: sqlserver-web
57 | EngineVersion: 14.00.3223.3.v1
58 | LicenseModel: license-included
59 | MultiAZ: false
60 | DBSubnetGroupName: !Ref UnicornStoreSubnetGroup
61 | VPCSecurityGroups:
62 | - Fn::GetAtt:
63 | - UnicornStoreRDSSecurityGroup
64 | - GroupId
65 | Tags:
66 | -
67 | Key: "Name"
68 | Value: "UnicornStoreDB"
69 | UnicornStoreSubnetGroup:
70 | Type: "AWS::RDS::DBSubnetGroup"
71 | Properties:
72 | DBSubnetGroupDescription: UnicornStore-SubnetGroup
73 | SubnetIds:
74 | - !GetAtt BasicVPC.Outputs.PublicSubnet1
75 | - !GetAtt BasicVPC.Outputs.PublicSubnet2
76 | UnicornStoreECR:
77 | Type: "AWS::ECR::Repository"
78 | Properties:
79 | RepositoryName : modernization-unicorn-store
80 | UnicornStoreCloudwatchLogGroup:
81 | Type: "AWS::Logs::LogGroup"
82 | Properties:
83 | LogGroupName: UnicornStore
84 | RetentionInDays: 30
85 | ECSExecutionRole:
86 | Type: AWS::IAM::Role
87 | Properties:
88 | AssumeRolePolicyDocument:
89 | Version: "2012-10-17"
90 | Statement:
91 | -
92 | Effect: "Allow"
93 | Principal:
94 | Service:
95 | - "ecs-tasks.amazonaws.com"
96 | Action:
97 | - "sts:AssumeRole"
98 | ManagedPolicyArns:
99 | - "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
100 | - "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
101 | Policies:
102 | -
103 | PolicyName: RetrieveUnicornSecret
104 | PolicyDocument:
105 | Version: "2012-10-17"
106 | Statement:
107 | - Effect: Allow
108 | Action:
109 | - secretsmanager:GetSecretValue
110 | Resource:
111 | - !Ref UnicornStoreDBSecret
112 | - !Ref DefaultAdminPasswordSecret
113 | - !Ref DefaultAdminUsernameSecret
114 | RoleName: "UnicornStoreExecutionRole"
115 | UnicornStoreLbSecurityGroup:
116 | Type: "AWS::EC2::SecurityGroup"
117 | Properties:
118 | GroupName: UnicornStoreLbSecurityGroup
119 | GroupDescription: Security group the the Unicornstore Application Load Balancer
120 | SecurityGroupIngress:
121 | - IpProtocol: tcp
122 | FromPort: 80
123 | ToPort: 80
124 | CidrIp: 0.0.0.0/0
125 | VpcId: !GetAtt BasicVPC.Outputs.VPCId
126 | UnicornStoreTaskSecurityGroup:
127 | Type: "AWS::EC2::SecurityGroup"
128 | Properties:
129 | GroupName: UnicornStoreTaskSecurityGroup
130 | GroupDescription: Security group the the Unicornstore Fargate Task
131 | VpcId: !GetAtt BasicVPC.Outputs.VPCId
132 | UnicornStoreTaskSecurityGroupIngress:
133 | Type: "AWS::EC2::SecurityGroupIngress"
134 | Properties:
135 | GroupId: !Ref UnicornStoreTaskSecurityGroup
136 | IpProtocol: tcp
137 | FromPort: 80
138 | ToPort: 80
139 | SourceSecurityGroupId: !Ref UnicornStoreLbSecurityGroup
140 | UnicornStoreLoadBalancer:
141 | Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
142 | Properties:
143 | Name: UnicornStore-LB
144 | Scheme: internet-facing
145 | SecurityGroups:
146 | - !Ref UnicornStoreLbSecurityGroup
147 | Subnets:
148 | - !GetAtt BasicVPC.Outputs.PublicSubnet1
149 | - !GetAtt BasicVPC.Outputs.PublicSubnet2
150 | Tags:
151 | - Key: Name
152 | Value: UnicornStore-LB
153 | Type: application
154 | IpAddressType: ipv4
155 | DependsOn: UnicornStoreLbSecurityGroup
156 | ECSCluster:
157 | Type: "AWS::ECS::Cluster"
158 | Properties:
159 | ClusterName: UnicornStoreCluster
160 | UnicornStoreDBSecret:
161 | Type: "AWS::SecretsManager::Secret"
162 | Properties:
163 | Name: UNICORNSTORE_DBSECRET
164 | Description: UnicornStoreDB RDS Secret
165 | SecretString:
166 | !Join
167 | - ''
168 | - - '{"username":'
169 | - !Sub '"${UnicornStoreDBUsername}",'
170 | - '"password":'
171 | - !Sub '"${UnicornStoreDBPassword}",'
172 | - '"engine":'
173 | - '"sqlserver",'
174 | - '"host":'
175 | - !Sub '"${UnicornStoreRDS.Endpoint.Address}",'
176 | - '"port":'
177 | - !Sub "${UnicornStoreRDS.Endpoint.Port},"
178 | - '"dbInstanceIdentifier":'
179 | - !Sub '"${UnicornStoreRDS}"'
180 | - '}'
181 | DefaultAdminPasswordSecret:
182 | Type: "AWS::SecretsManager::Secret"
183 | Properties:
184 | Name: DefaultAdminPassword
185 | Description: UnicornStore DefaultAdminPassword
186 | SecretString: Secret1*
187 | DefaultAdminUsernameSecret:
188 | Type: "AWS::SecretsManager::Secret"
189 | Properties:
190 | Name: DefaultAdminUsername
191 | Description: UnicornStore DefaultAdminUsername
192 | SecretString: Administrator@test.com
193 | Outputs:
194 | Cloud9IDE:
195 | Description: "The IDE Login URL"
196 | Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/cloud9/ide/${WorkshopIDE}"
197 |
198 |
--------------------------------------------------------------------------------
/content/secrets/cfn/player-vpc-template.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'This template will launch the Player Account VPC Environment for .Net Modernization Workshop.
4 | You will be billed for the AWS resources used if you create a stack from this template.'
5 | Mappings:
6 | SubnetConfig:
7 | VPC:
8 | CIDR: 10.0.0.0/16
9 | Public:
10 | CIDR: 10.0.0.0/24
11 | Public2:
12 | CIDR: 10.0.1.0/24
13 | Resources:
14 | VPC:
15 | Type: AWS::EC2::VPC
16 | Properties:
17 | EnableDnsSupport: 'true'
18 | EnableDnsHostnames: 'true'
19 | CidrBlock:
20 | Fn::FindInMap:
21 | - SubnetConfig
22 | - VPC
23 | - CIDR
24 | Tags:
25 | - Key: Name
26 | Value: "Demo VPC"
27 | - Key: Application
28 | Value:
29 | Ref: AWS::StackName
30 | - Key: Network
31 | Value: Public
32 | PublicSubnet1:
33 | Type: AWS::EC2::Subnet
34 | Properties:
35 | VpcId:
36 | Ref: VPC
37 | AvailabilityZone: !Select [ 0, !GetAZs '' ]
38 | CidrBlock:
39 | Fn::FindInMap:
40 | - SubnetConfig
41 | - Public
42 | - CIDR
43 | Tags:
44 | - Key: Application
45 | Value:
46 | Ref: AWS::StackName
47 | - Key: Network
48 | Value: Public
49 | PublicSubnet2:
50 | Type: AWS::EC2::Subnet
51 | Properties:
52 | VpcId:
53 | Ref: VPC
54 | AvailabilityZone: !Select [ 1, !GetAZs '' ]
55 | CidrBlock:
56 | Fn::FindInMap:
57 | - SubnetConfig
58 | - Public2
59 | - CIDR
60 | Tags:
61 | - Key: Application
62 | Value:
63 | Ref: AWS::StackName
64 | - Key: Network
65 | Value: Public
66 | InternetGateway:
67 | Type: AWS::EC2::InternetGateway
68 | Properties:
69 | Tags:
70 | - Key: Application
71 | Value:
72 | Ref: AWS::StackName
73 | - Key: Network
74 | Value: Public
75 | GatewayToInternet:
76 | Type: AWS::EC2::VPCGatewayAttachment
77 | Properties:
78 | VpcId:
79 | Ref: VPC
80 | InternetGatewayId:
81 | Ref: InternetGateway
82 | PublicRouteTable:
83 | Type: AWS::EC2::RouteTable
84 | Properties:
85 | VpcId:
86 | Ref: VPC
87 | Tags:
88 | - Key: Application
89 | Value:
90 | Ref: AWS::StackName
91 | - Key: Network
92 | Value: Public
93 | PublicRoute:
94 | Type: AWS::EC2::Route
95 | DependsOn: GatewayToInternet
96 | Properties:
97 | RouteTableId:
98 | Ref: PublicRouteTable
99 | DestinationCidrBlock: 0.0.0.0/0
100 | GatewayId:
101 | Ref: InternetGateway
102 | PublicSubnet1RouteTableAssociation:
103 | Type: AWS::EC2::SubnetRouteTableAssociation
104 | Properties:
105 | SubnetId:
106 | Ref: PublicSubnet1
107 | RouteTableId:
108 | Ref: PublicRouteTable
109 | PublicSubnet2RouteTableAssociation:
110 | Type: AWS::EC2::SubnetRouteTableAssociation
111 | Properties:
112 | SubnetId:
113 | Ref: PublicSubnet2
114 | RouteTableId:
115 | Ref: PublicRouteTable
116 | PublicNetworkAcl:
117 | Type: AWS::EC2::NetworkAcl
118 | Properties:
119 | VpcId:
120 | Ref: VPC
121 | Tags:
122 | - Key: Application
123 | Value:
124 | Ref: AWS::StackName
125 | - Key: Network
126 | Value: Public
127 | InboundPublicNetworkAclEntry:
128 | Type: AWS::EC2::NetworkAclEntry
129 | Properties:
130 | NetworkAclId:
131 | Ref: PublicNetworkAcl
132 | RuleNumber: '100'
133 | Protocol: -1
134 | RuleAction: allow
135 | Egress: 'false'
136 | CidrBlock: 0.0.0.0/0
137 | OutboundPublicNetworkAclEntry:
138 | Type: AWS::EC2::NetworkAclEntry
139 | Properties:
140 | NetworkAclId:
141 | Ref: PublicNetworkAcl
142 | RuleNumber: '100'
143 | Protocol: -1
144 | RuleAction: allow
145 | Egress: 'true'
146 | CidrBlock: 0.0.0.0/0
147 | PublicSubnet1NetworkAclAssociation:
148 | Type: AWS::EC2::SubnetNetworkAclAssociation
149 | Properties:
150 | SubnetId:
151 | Ref: PublicSubnet1
152 | NetworkAclId:
153 | Ref: PublicNetworkAcl
154 | PublicSubnet2NetworkAclAssociation:
155 | Type: AWS::EC2::SubnetNetworkAclAssociation
156 | Properties:
157 | SubnetId:
158 | Ref: PublicSubnet2
159 | NetworkAclId:
160 | Ref: PublicNetworkAcl
161 | Outputs:
162 | VPCId:
163 | Description: VPCId of the newly created VPC
164 | Value:
165 | Ref: VPC
166 | PublicSubnet1:
167 | Description: SubnetId of the public subnet 1
168 | Value:
169 | Ref: PublicSubnet1
170 | PublicSubnet2:
171 | Description: SubnetId of the public subnet 2
172 | Value:
173 | Ref: PublicSubnet2
174 | VPCCIDR:
175 | Description: VPC CIDR CidrBlock
176 | Value:
177 | Fn::FindInMap:
178 | - SubnetConfig
179 | - VPC
180 | - CIDR
--------------------------------------------------------------------------------
/content/secrets/container-registry.md:
--------------------------------------------------------------------------------
1 | # Container Registry
2 |
3 | Now that we've successfully containerized the Unicorn Store application, it's time to push the image to [Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/) before we can deploy our application to an orchestrator like AWS Fargate.
4 |
5 | Amazon Elastic Container Registry (ECR) is a fully-managed Docker container registry that makes it easy for developers to store, manage, and deploy Docker container images. Amazon Elastic Container Registry integrates with Amazon ECS and the Docker CLI, allowing you to simplify your development and production workflows. You can easily push your container images to Amazon ECR using the Docker CLI from your development machine, and Amazon ECS can pull them directly for production deployments.
6 |
7 | To get started, log into your Amazon ECR registry using the helper provided by the AWS CLI:
8 |
9 | ```
10 | eval $(aws ecr get-login --no-include-email)
11 | ```
12 |
13 | Use the AWS CLI to get information about the Amazon ECR repository for the Unicorn Store that was created for you ahead of time:
14 |
15 | ```
16 | aws ecr describe-repositories --repository-name modernization-unicorn-store
17 | ```
18 |
19 | 
20 |
21 | Verify that the modernization-unicorn-store_unicornstore:latest image exists by running the following command:
22 |
23 | ```
24 | docker images
25 | ```
26 |
27 | 
28 |
29 |
30 | The next step is to tag your image so you can push the image to the repository by running the following command:
31 |
32 | ```
33 | docker tag modernization-unicorn-store_unicornstore:latest $(aws ecr describe-repositories --repository-name modernization-unicorn-store --query=repositories[0].repositoryUri --output=text):latest
34 | ```
35 |
36 | Run the following command to push your image to the repository:
37 |
38 | ```
39 | docker push $(aws ecr describe-repositories --repository-name modernization-unicorn-store --query=repositories[0].repositoryUri --output=text):latest
40 | ```
41 |
42 | With the Unicorn Store image now pushed to Amazon ECR, we are ready to deploy it to AWS Fargate.
43 |
44 | Click [**here**](/content/secrets/taskdefinitions.md) to move to the next section where we will create the Task Definition for the Unicorn Store to run in AWS Fargate.
45 |
--------------------------------------------------------------------------------
/content/secrets/containerize-unicornstore.md:
--------------------------------------------------------------------------------
1 | # Containerize the Unicorn Store
2 |
3 | Now that we've successfully launched the Unicorn Store in our local development environment, it's time to containerize the application to get it ready to deploy to AWS Fargate. We will be using AWS Secrets Manager to inject the secrets into the container when it is initially started. The secrets will be injected as environment variables containing the sensitive data to present to the container.
4 |
5 | If you remember from earlier in the lab, environment variables can be accessed via the the Configuration Provider in .NET Core. This means we can containerize the application without any sensitive information stored inside the container. When the container is launched, we can inject the senstive information in the format that the application is expecting.
6 |
7 | To build our container, we will use [Docker Compose](https://docs.docker.com/compose/) which is a tool for defining and running multi-container Docker applications. In the root of the modernization-unicorn-store repository you will see a file called [docker-compose.yml](https://github.com/aws-samples/modernization-unicorn-store/blob/master/docker-compose.yml). Feel free to review the contents. You will notice it uses [Dockerfile](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/Dockerfile) in the UnicornStore project to build the application into a container. Issue the following command to build the container image:
8 |
9 | ```
10 | cd ~/environment/modernization-unicorn-store/
11 |
12 | docker-compose build
13 | ```
14 |
15 | Once the image has been successfully built you should be able to see the modernization-unicorn-store_unicornstore:latest image by issuing the following command:
16 |
17 | ```
18 | docker images
19 | ```
20 |
21 | 
22 |
23 | At this point, you can launch the container locally with docker-compose. You'll notice that in the root of the modernization-unicorn-store repository there are two files named [docker-compose.development.yml](https://github.com/aws-samples/modernization-unicorn-store/blob/master/docker-compose.development.yml) and [docker-compose.production.yml](https://github.com/aws-samples/modernization-unicorn-store/blob/master/docker-compose.production.yml). A common use case for multiple compose files is changing a development Compose app for a production-like environment. Keep in mind, you should ***NEVER*** store sensitive information in plain text in those files so they aren't accidentally committed to a source code repository. To showcase how multiple Compose files can be used together try uncommenting the lines in ***docker-compose.development.yml*** so they look like the below example and save the file.
24 |
25 | You will need to replace the db specific information in the UNICORNSTORE_DBSECRET key with your specific information that you noted down. If you forgot to write them down, simply issue the following command:
26 |
27 | ```
28 | dotnet user-secrets list -p ~/environment/modernization-unicorn-store/UnicornStore/
29 |
30 | ```
31 |
32 | To edit the docker-compose-development.yml file with your values simply double click it on the left navigation tree in Cloud9 which opens up a text editor. Make sure you replace the following values in the UNICORNSTORE_DBSECRET json string in the docker-compose-development.yml.
33 |
34 | * username
35 | * password
36 | * host
37 | * dbInstanceIdentifier
38 |
39 | The file should look like the below once edited and saved with your values.
40 |
41 | 
42 |
43 | Now run the following command:
44 |
45 | ```
46 | docker-compose -f docker-compose.yml -f docker-compose.development.yml up
47 | ```
48 |
49 | You should be able to navigate to the Unicorn Store by clicking the ***Preview, Preview Running Application*** button on the menu bar in the AWS Cloud9 IDE. However, now the application is running in a local container. The necessary configuration has now been inserted as environment variables when the container was started just like when we go to launch it in Fargate.
50 |
51 | Go ahead and stop the running .NET application by issuing a ***Ctrl+C*** in the Cloud9 Terminal.
52 |
53 | Click [**here**](/content/secrets/container-registry.md) to move to the next section where we will push the Unicorn Store Docker image to Amazon Elastic Container Registry.
--------------------------------------------------------------------------------
/content/secrets/create-secrets.md:
--------------------------------------------------------------------------------
1 | # Create Secrets
2 |
3 | Now that you understand the basics of the .NET Core Secrets Manager tool, let's create two more secrets that are needed for the Unicorn Store. If you look at the [appsettings.Development.json](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/appsettings.Development.json) or [appsettings.Production.json](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/appsettings.Production.json) files, you'll notice two keys with the name ***DefaultAdminUsername*** and ***DefaultAdminPassword*** but neither have values. While you could set the values here directly, it would be insecure because then the files would be checked into source control with sensitive information inside.
4 |
5 | 
6 |
7 | When the lab was provisioned, the CloudFormation template created and stored both of those names in AWS Secrets Manager with a secret-id of ***DefaultAdminUsername*** and ***DefaultAdminUsername*** with a pre-set SecretString for each. We will use the values for SecretString locally in our testing via the .NET Core Secrets Manager tool. We will use AWS Secrets Manager later on in this lab when we deploy the Unicorn Store to AWS Fargate.
8 |
9 | Let's set up the two secrets locally by issuing the following commands:
10 |
11 | .NET Core Secrets Manager
12 |
13 | ```
14 | cd ~/environment/modernization-unicorn-store/UnicornStore
15 |
16 | AdminUser=$(aws secretsmanager get-secret-value --secret-id DefaultAdminUsername | jq -r '.SecretString')
17 |
18 | dotnet user-secrets set "DefaultAdminUsername" $AdminUser
19 |
20 | AdminPass=$(aws secretsmanager get-secret-value --secret-id DefaultAdminPassword | jq -r '.SecretString')
21 |
22 | dotnet user-secrets set "DefaultAdminPassword" $AdminPass
23 | ```
24 |
25 | Now that we have all of our secrets set up for local testing, let's look at the local secrets to ensure everything is ready. Your secrets should be the same as below but have your unique secret values:
26 |
27 | ```
28 | dotnet user-secrets list
29 | ```
30 |
31 | 
32 |
33 | Take note of all of the values by taking a screenshot or writing them down. You will need them later in this lab.
34 |
35 | To test out the Unicorn Store in our local development environment using .NET Core Secrets Manager you can simply run the below command or run the command in an IDE like Visual Studio:
36 |
37 | ```
38 | dotnet run
39 | ```
40 |
41 | After a couple of seconds, you will see a message similar to the below.
42 |
43 |
44 | However, in order to preview the application in Cloud9, click the ***Preview, Preview Running Application*** button on the menu bar in the AWS Cloud9 IDE.
45 |
46 | 
47 |
48 | This opens an application preview tab within the environment, and then displays the application's output on the tab. Click the ***Pop Out Into New Window*** button on the preview tab so the page loads into a larger window.
49 |
50 | 
51 |
52 | Try clicking the admin link at the bottom of the page and logging in with the secret values you set for ***DefaultAdminUsername*** and ***DefaultAdminPassword***.
53 |
54 | We've now successfully configured our development environment to authenticate to our Unicorn Store RDS instance. You may be wondering who created the database and how the application connected to it at this point. In the UnicornStore project, there is a class named [DBInitializer.cs](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/Data/DbInitializer.cs) in the Data folder that gets called via [Program.cs](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/Program.cs) when the application starts. If the database doesn't exist, the application creates it and populates it with sample data. The application connects to the database with the credentials you specified in .NET Secrets Manager because the application is running with an environment variable of ***"ASPNETCORE_ENVIRONMENT": "Development"*** which is set when you ran the application.
55 |
56 | Go ahead and stop the running .NET application by issuing a ***Ctrl+C*** in the Cloud9 Terminal.
57 |
58 | Click [**here**](/content/secrets/containerize-unicornstore.md) to move to the next section where we will containerize the Unicorn Store and run it in Cloud9 using environment variables from Docker Compose.
--------------------------------------------------------------------------------
/content/secrets/fargate.md:
--------------------------------------------------------------------------------
1 | # Fargate
2 |
3 | Now that we've successfully registered our Task Definition, it's time to deploy the Unicorn Store to [AWS Fargate](https://aws.amazon.com/fargate/).
4 |
5 | To start, run the below commands to set some variables in order to successfully complete this chapter and run subsequent commands in the AWS CLI:
6 |
7 | ```
8 | UnicornVPCID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values="Demo VPC" --query="Vpcs[0].VpcId" --output=text)
9 |
10 | TaskSecurityGroup=$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=$UnicornVPCID Name=group-name,Values=UnicornStoreTaskSecurityGroup --query "SecurityGroups[0].GroupId" --output=text)
11 |
12 | UnicornSubnet1=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$UnicornVPCID --query "Subnets[0].SubnetId" --output=text)
13 |
14 | UnicornSubnet2=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$UnicornVPCID --query "Subnets[1].SubnetId" --output=text)
15 |
16 | ```
17 |
18 | Now let's create a Target Group for the Application Load Balancer. When the lab was provisioned, the CloudFormation template created an Application Load Balancer with a name of ***UnicornStore-LB*** in your account. Notice the value of ***--target-type ip*** in the below command. This is because we are using the Fargate launch type and the task will use the awsvpc network mode. This is mandatory because tasks that use awsvpc network mode are associated with an elastic network interface, not an Amazon EC2 instance.
19 |
20 | ```
21 | aws elbv2 create-target-group --name ecs-Unicor-UnicornStore-Service --protocol HTTP --port 80 --vpc-id $UnicornVPCID --health-check-path "/health" --target-type ip
22 | ```
23 |
24 | Let's create a listener for the Application Load Balancer. A listener is a process that checks for connection requests, using the protocol and port that you configure. The rules that you define for a listener determine how the load balancer routes requests to the targets in one or more target groups.
25 |
26 | ```
27 | LBARN=$(aws elbv2 describe-load-balancers --names="UnicornStore-LB" --query="LoadBalancers[0].LoadBalancerArn" --output=text)
28 |
29 | TGARN=$(aws elbv2 describe-target-groups --names ecs-Unicor-UnicornStore-Service --query="TargetGroups[0].TargetGroupArn" --output=text)
30 |
31 | aws elbv2 create-listener --load-balancer-arn $LBARN --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn=$TGARN
32 |
33 | ```
34 |
35 | Now that the load balancer is ready, let's create the ECS Fargate Service for the Unicorn Store.
36 |
37 | ```
38 | aws ecs create-service --cluster UnicornStoreCluster --service-name UnicornStore-Service --task-definition modernization-unicorn --desired-count 1 --launch-type "FARGATE" --network-configuration "awsvpcConfiguration={subnets=[$UnicornSubnet1, $UnicornSubnet2],securityGroups=[$TaskSecurityGroup],assignPublicIp=ENABLED}" --load-balancers targetGroupArn=$TGARN,containerName=modernization-unicorn-store_unicornstore,containerPort=80
39 |
40 | ```
41 |
42 | Run the below commands to get the URL for the Unicorn Store behind the ALB and paste it into your browser. It may take up to a minute or so for the initial registration and the URL to the Unicorn Store to be healthy and ready to serve traffic:
43 |
44 | ```
45 | LBDNS=$(printf "http://%s\n" $(aws elbv2 describe-load-balancers --names="UnicornStore-LB" --query="LoadBalancers[0].DNSName" --output=text))
46 |
47 | until [[ `aws elbv2 describe-target-health --target-group-arn $TGARN --query "TargetHealthDescriptions[0].[TargetHealth]" --output text` == "healthy" ]]; do echo "The Unicorn Store is NOT registered with the Target Group at `date`"; sleep 10; done && echo "The Unicorn Store is ready at `date` - Please proceed to $LBDNS"
48 | ```
49 |
50 | ***CONGRATULATIONS!!!*** You've now successfully deployed the ASP.NET Core Unicorn Store to AWS Fargate with the Production configuration coming from AWS Secrets Manager!
51 |
52 | 
53 |
54 |
55 |
--------------------------------------------------------------------------------
/content/secrets/introduction.md:
--------------------------------------------------------------------------------
1 | # Secrets Introduction
2 |
3 | When designing an application, one of the primary considerations is how sensitive data will be stored. A very common use-case is ensuring that something like the password and information needed to connect to your database aren’t written to a file or checked into a source control repository. This information should be considered sensitive and should only be accessed by the users and applications in a least privileged model.
4 |
5 | In this section, we will understand how to how to leverage [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) which helps you protect secrets that are needed for your applications and makes it easy to manage and things like database credentials to name a few.
6 |
7 | We will also cover the concept of using a .NET Core tool called [Secret Manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.0&tabs=windows) which allows you to store sensitive data during the development of your application. AWS Secrets Manager and .NET Core Secret Manager should never be confused but we will show you how to use both when designing your .NET Core application. By leveraging the .NET Core Secret Manager tool, a developer can easily create key/value objects in a JSON file on their local machine outside of the actual project to ensure the actual secrets aren’t checked into a source control repository.
8 |
9 | Click [**here**](/content/secrets/secrets.md) to move to the next section where we will explain the basics of the ASP.NET Core Secret Manager tool and create the UNICORNSTORE_DBSECRET.
10 |
--------------------------------------------------------------------------------
/content/secrets/prerequisites/_index.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | To start the workshop, follow one of the below links depending on whether you are...
4 |
5 | * ...[running the workshop on your own (in your own account)](self_paced/_index.md)
6 | * ...[attending an AWS hosted event (using AWS provided hashes)](aws_event/_index.md)
7 |
8 | Once you have completed with either setup, continue with [**Create a Workspace**](getting-started.md)
--------------------------------------------------------------------------------
/content/secrets/prerequisites/aws_event/_index.md:
--------------------------------------------------------------------------------
1 | # Running the workshop at an AWS Event
2 |
3 | Only complete this section if you are at an AWS hosted event (such as re:Invent, re:Inforce, Immersion Day or any other event hosted by an AWS employee). The instructions are geared towards using the AWS Workshop Portal which pre-provisions the resources needed to complete the workshop. Please make sure that the AWS employee stated this is how the workshop will be run. If not, then go to [start the workshop on your own](/content/secrets/prerequisites/self_paced/_index.md). Otherwise, please proceed to the below.
4 |
5 | * ...[Instructions on how to login to the AWS Workshop Portal](portal.md)
6 |
--------------------------------------------------------------------------------
/content/secrets/prerequisites/aws_event/portal.md:
--------------------------------------------------------------------------------
1 | # Login to AWS Workshop Portal
2 |
3 | This workshop creates an AWS account and a Cloud9 environment. You will need the **Participant Hash** provided upon entry, and your email address to track your unique session.
4 |
5 | Connect to the portal by clicking the button or browsing to [https://portal.awsworkshop.io/](https://portal.awsworkshop.io/).
6 |
7 |
10 | Connect to Portal
11 |
12 |
13 |
14 | 
15 |
16 | Enter your **Participant Hash** and your email address, and click **Log In**.
17 |
18 | Once you have been logged in, please first log into the AWS console by clicking on the button. Once you have successfully logged into the AWS Console, you can open the Cloud9 IDE by clicking on the button.
19 |
20 | 
21 |
22 | The workshop added an IAM role for performing all the steps of the workshop in the Cloud9 Environment. You do not need to add a role to the instance powering the Cloud9 Environment.
23 |
24 |
25 | Once you have completed the step above, you can head straight to [**Getting Started**](/content/secrets/prerequisites/getting-started.md)
--------------------------------------------------------------------------------
/content/secrets/prerequisites/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting Started with this Workshop
2 |
3 | In order for you to succeed in this workshop, we need you to run through a few steps to finalize the configuration of your Cloud9 environment. You could do this workshop in your own environment using an IDE like Visual Studio, but for a consistent experience for all users, we will walk through setting up a Cloud9 environment instead. If you launched the CloudFormation resources in this module manually or are running this at an AWS Event, a Cloud9 environment has been provisioned for you.
4 |
5 | #### Update and install some tools
6 |
7 | The first step is to update the `AWS CLI`, `pip` and a range of pre-installed packages.
8 |
9 | ```
10 | sudo yum update -y && pip install --upgrade --user awscli pip
11 | exec $SHELL
12 | ```
13 |
14 | #### Configure the AWS Environment
15 |
16 | After you have the installed the latest awscli and pip we need to configure our environment to use us-west-2
17 |
18 | ```
19 | aws configure set region us-west-2
20 |
21 | ```
22 |
23 | #### Install the .NET Core SDK
24 |
25 | 1. In this step, you install the .NET Core 3 SDK into your environment, which is required to run this sample. Run the below command to install a ***libunwind*** package that the .NET Core 3 SDK needs.
26 |
27 | ```
28 | sudo yum -y install libunwind
29 | ```
30 |
31 | 2. Download the .NET Core 3 SDK installer script into your environment by running the following command.
32 |
33 | ```
34 | curl -O -L https://dot.net/v1/dotnet-install.sh
35 | ```
36 |
37 | 3. Make the installer script executable by the current user by running the following command.
38 |
39 | ```
40 | sudo chmod u=rx dotnet-install.sh
41 | ```
42 |
43 | 4. Run the installer script, which downloads and installs the .NET Core 3 SDK, by running the following command.
44 |
45 | ```
46 | ./dotnet-install.sh -c Current
47 | ```
48 |
49 | 5. Add the .NET Core 3 SDK to your PATH. To do this, in the shell profile for the environment (for example, the .bashrc file), add the $HOME/.dotnet subdirectory to the PATH variable for the environment, as follows.
50 |
51 | * Open the .bashrc file for editing by using the vi command.
52 |
53 | ```
54 | vi ~/.bashrc
55 | ```
56 |
57 | * Using the down arrow or j key, move to the line that starts with ***export PATH***.
58 |
59 | * Using the right arrow or $ key, move to the end of that line.
60 |
61 | * Switch to insert mode by pressing the i key. (-- INSERT --- will appear at the end of the display.)
62 |
63 | * Add the $HOME/.dotnet subdirectory to the PATH variable by typing :$HOME/.dotnet. Be sure to include the colon character (:). The line should now look similar to the following.
64 |
65 | 
66 |
67 | * Save the file. To do this, press the Esc key (-- INSERT --- will disappear from the end of the display), type :wq (to write to and then quit the file), and then press Enter.
68 |
69 | 6. Load the .NET Core 3 SDK by sourcing the .bashrc file.
70 |
71 | ```
72 | . ~/.bashrc
73 | ```
74 |
75 | 7. Confirm the .NET Core 3 SDK is loaded by running .NET Core CLI with the --help option.
76 |
77 | ```
78 | dotnet --help
79 | ```
80 |
81 | 8. If successful, the .NET Core 3 SDK version number is displayed, with additional usage information.
82 |
83 | #### Clone the source repository for this workshop
84 |
85 | Now we want to clone the repository that contains all of the content and files you need to complete this workshop.
86 |
87 | ```
88 | cd ~/environment
89 |
90 | git clone https://github.com/aws-samples/modernization-unicorn-store.git
91 | ```
92 |
93 | #### Installing Docker Compose
94 |
95 | For this workshop we use the tool [Docker Compose](https://docs.docker.com/compose/) which is a tool for defining and running multi-container Docker applications.
96 |
97 | 1. Run this command to download the current stable release of Docker Compose:
98 |
99 | ```
100 | sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
101 | ```
102 |
103 | 2. Apply executable permissions to the binary:
104 |
105 | ```
106 | sudo chmod +x /usr/local/bin/docker-compose
107 | ```
108 |
109 | 3. Test the installation:
110 | ```
111 | docker-compose --version
112 | ```
113 |
114 | #### Install JQ
115 |
116 | We will also be using a tool called [jq](https://stedolan.github.io/jq/) which is a lightweight and flexible command-line JSON processor.
117 | ```
118 | sudo yum -y install jq
119 | ```
120 |
121 | #### Clean up space on Cloud9 IDE
122 |
123 | We are using one of the smaller Cloud9 instances and space is limited so we can clean up some space by removing these docker images we won't be using by running the following command:
124 |
125 | ```
126 | docker rmi $(docker images -q)
127 | ```
128 |
129 | #### Congratulations
130 |
131 | You now have a fully working Cloud9 IDE that is ready to use! Click [**here**](/content/secrets/introduction.md) to start learning about secrets.
132 |
--------------------------------------------------------------------------------
/content/secrets/prerequisites/self_paced/_index.md:
--------------------------------------------------------------------------------
1 | # Running the workshop on your own
2 |
3 | Only complete this section if you are running the workshop on your own.
4 |
5 | * ...[Create an AWS account](account.md)
--------------------------------------------------------------------------------
/content/secrets/prerequisites/self_paced/account.md:
--------------------------------------------------------------------------------
1 | # Create an AWS account
2 |
3 | #### Your account must have the ability to create new IAM roles and scope other IAM permissions.
4 |
5 | 1. If you don't already have an AWS account with Administrator access: [create one now by clicking here](https://aws.amazon.com/getting-started/)
6 |
7 | 2. Once you have an AWS account, ensure you are following the remaining workshop steps as an IAM user with administrator access to the AWS account:
8 | [Create a new IAM user to use for the workshop](https://console.aws.amazon.com/iam/home?#/users$new)
9 |
10 | 3. Enter the user details:
11 | 
12 |
13 | 4. Attach the AdministratorAccess IAM Policy:
14 | 
15 |
16 | 5. Click to create the new user:
17 | 
18 |
19 | 6. Take note of the login URL and save:
20 | 
21 |
22 |
23 | You are now ready to launch the CloudFormation resources needed for this workshop. Click [**here**](cloudformation.md) to move to the next section where we will launch the CloudFormation resources.
24 |
--------------------------------------------------------------------------------
/content/secrets/prerequisites/self_paced/cloudformation.md:
--------------------------------------------------------------------------------
1 | # Launch the CloudFormation resources
2 |
3 | To get started, you will need a S3 bucket to host the CloudFormation nested stack template for the VPC. To do this you can either create the bucket through the console and upload the template yourself or follow the below instructions using the AWS CLI.
4 |
5 | 1. Create the bucket. This workshop runs out of the us-west-2 so the `--region` flag is important so don't change it. Also, make sure you give a unique name for the `--bucket` value and write it down for later use.
6 |
7 | ```
8 | aws s3api create-bucket --bucket modernization-unicorn-store --region us-west-2 --create-bucket-configuration LocationConstraint=us-west-2
9 | ```
10 |
11 | 2. If you haven't already done so, download the two CloudFormation templates in the `content/secrets/cfn` directory in this repository to your local machine or just clone the repository.
12 |
13 | 3. Change to the `content/secrets/cfn` directory in this repository and upload the `player-vpc-template` to the s3 bucket you just created. Only change the `--bucket` value to your bucket. Keep the rest of the command as is.
14 |
15 | ```
16 | aws s3api put-object --bucket modernization-unicorn-store --key modules/modernization/unicorn-store/templates/player-vpc-template.yaml --body player-vpc-template.yaml
17 | ```
18 |
19 | 4. Deploy the CloudFormation template by running the below comannd. Only change the value for the `ParameterValue` key to your bucket.
20 |
21 | ```
22 | aws cloudformation create-stack \
23 | --stack-name modernization-unicorn-store \
24 | --parameters ParameterKey=BucketName,ParameterValue=modernization-unicorn-store ParameterKey=BucketPrefix,ParameterValue=modules/modernization/unicorn-store \
25 | --template-body file://player-template.yaml \
26 | --capabilities CAPABILITY_NAMED_IAM \
27 | --region us-west-2
28 | ```
29 |
30 | 5. The CloudFormation stack takes a while to run due to it provisioning a RDS instance. Run this command and wait for the CloudFormation template to finish deploying.
31 |
32 | ```
33 | until [[ `aws cloudformation describe-stacks --stack-name "modernization-unicorn-store" --region us-west-2 --query "Stacks[0].[StackStatus]" --output text` == "CREATE_COMPLETE" ]]; do echo "The stack is NOT in a state of CREATE_COMPLETE at `date`"; sleep 30; done && echo "The Stack is built at `date` - Please proceed"
34 | ```
35 |
36 | Once you have completed with either setup, continue with [**Create a Workspace**](/content/secrets/prerequisites/getting-started.md)
--------------------------------------------------------------------------------
/content/secrets/secrets.md:
--------------------------------------------------------------------------------
1 | # Secrets
2 |
3 | To get started, you will need certain secrets for the Unicorn Store to function. Since our end goal is to deploy the Unicorn Store into a container that retrieves the connection string to RDS from AWS Secrets Manager and other sensitive information, let’s first understand the format of the secret. The good news is both AWS Secrets Manager and .NET Core Secret Manager store the secret in JSON. When the lab was provisioned, the CloudFormation template created and stored a secret in AWS Secrets Manager with a secret-id of **UNICORNSTORE_DBSECRET** that contains the credentials to the UnicornStoreRDS AWS::RDS::DBInstance. Below is an example of a secret for the credentials to our Unicorn Store RDS Database from AWS Secrets Manager.
4 |
5 | 
6 |
7 | For .NET Core secrets in the Secret Manager tool, the values are stored in a JSON configuration file in a system-protected user profile folder on the local machine:
8 |
9 | | Linux/macOS File system path: | Windows File system path: |
10 | |---------------------------------------------------------|----------------------------------------------------------------|
11 | | ~/.microsoft/usersecrets//secrets.json | %APPDATA%\Microsoft\UserSecrets\\secrets.json |
12 |
13 | The value is the value that is defined in the [UnicornStore.csproj](https://github.com/aws-samples/modernization-unicorn-store/blob/master/UnicornStore/UnicornStore.csproj) file. This element has to be set in order to use user secrets in .NET Core. We have already set this value for you as defined below:
14 |
15 | 
16 |
17 | Let's populate our local secrets.json development file for the .NET Core Secret Manager tool. We are going to query AWS Secrets Manager to retrieve the UNICORNSTORE_DBSECRET and populate our local development environment with the values so we can connect to our test database in RDS. Go ahead and run the below commands:
18 |
19 | ```
20 | AWSSECRET=$(aws secretsmanager get-secret-value --secret-id UNICORNSTORE_DBSECRET | jq -r '.SecretString')
21 |
22 | SECRET=$(printf '{"UNICORNSTORE_DBSECRET": %s}\n' $AWSSECRET)
23 |
24 | mkdir -p ~/.microsoft/usersecrets/45b651b1-da6a-44fb-af93-525b292efddb/
25 |
26 | echo $SECRET | jq . > ~/.microsoft/usersecrets/45b651b1-da6a-44fb-af93-525b292efddb/secrets.json
27 | ```
28 |
29 | Check to make sure the file was created and the contents contain the secret. The format should be the same as above but have your unique secret values.
30 |
31 | ```
32 | cat ~/.microsoft/usersecrets/45b651b1-da6a-44fb-af93-525b292efddb/secrets.json
33 | ```
34 |
35 | As you can see, the secrets.json file is outside of your project meaning they will not be committed to any source control systems!
36 |
37 | Click [**here**](/content/secrets/accessing-secrets.md) to move to the next section where we will explain how to access secrets in ASP.NET Core Secret Manager via the Configuration API.
--------------------------------------------------------------------------------
/content/secrets/taskdefinitions.files/modernization-unicorn-store-task-definition.json:
--------------------------------------------------------------------------------
1 | {
2 | "family": "modernization-unicorn",
3 | "networkMode": "awsvpc",
4 | "containerDefinitions": [
5 | {
6 | "name": "modernization-unicorn-store_unicornstore",
7 | "image": ".dkr.ecr.us-west-2.amazonaws.com/modernization-unicorn-store:latest",
8 | "cpu": 512,
9 | "memoryReservation": 1024,
10 | "portMappings": [{
11 | "containerPort": 80
12 | }],
13 | "environment": [
14 | {
15 | "name": "ASPNETCORE_ENVIRONMENT",
16 | "value": "Production"
17 | }
18 | ],
19 | "secrets": [
20 | {
21 | "name": "UNICORNSTORE_DBSECRET",
22 | "valueFrom": ""
23 | },
24 | {
25 | "name": "DefaultAdminUsername",
26 | "valueFrom": ""
27 |
28 | },
29 | {
30 | "name": "DefaultAdminPassword",
31 | "valueFrom" : ""
32 | }
33 | ],
34 | "logConfiguration": {
35 | "logDriver": "awslogs",
36 | "options": {
37 | "awslogs-group": "UnicornStore",
38 | "awslogs-region": "us-west-2",
39 | "awslogs-stream-prefix": "web"
40 | }
41 | }
42 | }
43 | ],
44 | "executionRoleArn": "arn:aws:iam:::role/UnicornStoreExecutionRole",
45 | "requiresCompatibilities": [
46 | "FARGATE"
47 | ],
48 | "cpu": "1 vcpu",
49 | "memory": "2 gb"
50 | }
51 |
--------------------------------------------------------------------------------
/content/secrets/taskdefinitions.md:
--------------------------------------------------------------------------------
1 | # Task Definitions
2 |
3 | ## Introduction
4 |
5 | [Amazon ECS Task Definitions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html) are required to run Docker containers in Amazon ECS.
6 |
7 | ## Create the Unicorn Store Task Definition
8 |
9 | Copy/Paste the following commands into your terminal.
10 |
11 | ```
12 |
13 | cd ~/environment/modernization-unicorn-store/content/secrets/taskdefinitions.files/
14 |
15 | ```
16 |
17 | Check the configuration of modernization-unicorn-store-task-definition.json by issuing the following command:
18 |
19 | ```
20 | cat modernization-unicorn-store-task-definition.json | jq
21 | ```
22 |
23 | You can see a couple of interesting key/values in this json file that defines our application. One of the most important sections is the ***secrets*** section. You will see here this is how are accessing AWS Secrets Manager. To inject sensitive data into your containers as environment variables, use the secrets container definition parameter.
24 |
25 | 
26 |
27 | We need to replace the placeholders for your account id and the individual arn's of each secret in the task definition template file so that it works in your account. The below commands do it automatically for you:
28 |
29 | Insert the AccountID for the image and the executionRoleArn:
30 |
31 | ```
32 | ACCOUNT_ID=$(aws ecr describe-repositories --repository-name modernization-unicorn-store | jq -r '.repositories[] | {registryId}' | jq --raw-output '.registryId')
33 |
34 | echo $ACCOUNT_ID
35 |
36 | sed -i "s//${ACCOUNT_ID}/" modernization-unicorn-store-task-definition.json
37 | ```
38 |
39 | Insert the UNICORNSTORE_DBSECRET Secrets Manager Arn:
40 |
41 | ```
42 | DBSECRET_ARN=$(aws secretsmanager describe-secret --secret-id UNICORNSTORE_DBSECRET | jq -r '.ARN')
43 |
44 | echo $DBSECRET_ARN
45 |
46 | sed -i "s//${DBSECRET_ARN}/" modernization-unicorn-store-task-definition.json
47 | ```
48 |
49 | Insert the DefaultAdminUsername Secrets Manager Arn:
50 |
51 | ```
52 | AdminSecret_ARN=$(aws secretsmanager describe-secret --secret-id DefaultAdminUsername | jq -r '.ARN')
53 |
54 | echo $AdminSecret_ARN
55 |
56 | sed -i "s//${AdminSecret_ARN}/" modernization-unicorn-store-task-definition.json
57 | ```
58 |
59 | Insert the DefaultAdminPassword Secrets Manager Arn:
60 |
61 | ```
62 | PasswordSecret_ARN=$(aws secretsmanager describe-secret --secret-id DefaultAdminPassword | jq -r '.ARN')
63 |
64 | echo $PasswordSecret_ARN
65 |
66 | sed -i "s//${PasswordSecret_ARN}/" modernization-unicorn-store-task-definition.json
67 | ```
68 |
69 | Go ahead and inspect the modernization-unicorn-store-task-definition.json file again to see that the configuration has been updated by issuing the following command:
70 |
71 | ```
72 | cat modernization-unicorn-store-task-definition.json | jq
73 | ```
74 |
75 | Now we can register the task definition with ECS from the JSON file by running the following command:
76 |
77 | ```
78 | aws ecs register-task-definition --cli-input-json file://modernization-unicorn-store-task-definition.json
79 | ```
80 |
81 | ## Required IAM Permissions for Amazon ECS Secrets
82 |
83 | You may be wondering how the task definition is going to access the secret at this point. It's as simple as adding a policy to the UnicornStoreExecutionRole IAM role that is specified in the task definition. When the lab was provisioned, the CloudFormation template created the IAM role for you with the appropriate permissions to provide access to the Secrets Manager resources.
84 |
85 | Feel free to explore the IAM Role for UnicornStoreExecutionRole in the AWS console or via the cli and look at the ***RetrieveUnicornSecret*** Inline policy. It should have something similar to below.
86 |
87 | 
88 |
89 | Click [**here**](/content/secrets/fargate.md) to move to the next section where we will create the service in AWS Fargate to run the Unicorn Store.
90 |
--------------------------------------------------------------------------------
/docker-compose.development.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | services:
4 |
5 | unicornstore:
6 | environment:
7 | - ASPNETCORE_ENVIRONMENT=Development
8 | # The below environment variables are an example.
9 | # Never store passwords or other sensitive data in configuration provider code or in plain text configuration files.
10 | # Don't use production secrets in development or test environments.
11 | # Specify secrets outside of the project so that they can't be accidentally committed to a source code repository.
12 | # You need to UNCOMMENT the below and insert your db specific information.
13 | #- UNICORNSTORE_DBSECRET={"username":"unicorndbuser","password":"unicorndbpassword","engine":"sqlserver","host":"unicorn-db-us-west-2.rds.amazonaws.com","port":1433,"dbInstanceIdentifier":"unicorn-db"}
14 | #- DefaultAdminUsername=Administrator@test.com
15 | #- DefaultAdminPassword=Secret1*
16 | healthcheck:
17 | test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/health"]
18 | interval: 30s
19 | timeout: 5s
20 | retries: 5
21 | start_period: 30s
22 |
--------------------------------------------------------------------------------
/docker-compose.production.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | services:
4 |
5 | unicornstore:
6 | environment:
7 | - ASPNETCORE_ENVIRONMENT=Production
8 | # The below environment variables are an example.
9 | # Never store passwords or other sensitive data in configuration provider code or in plain text configuration files.
10 | # Don't use production secrets in development or test environments.
11 | # Specify secrets outside of the project so that they can't be accidentally committed to a source code repository.
12 | # You need to UNCOMMENT the below and insert your db specific information.
13 | #- UNICORNSTORE_DBSECRET={"username":"unicorndbuser","password":"unicorndbpassword","engine":"sqlserver","host":"unicorn-db-us-west-2.rds.amazonaws.com","port":1433,"dbInstanceIdentifier":"unicorn-db"}
14 | #- DefaultAdminUsername=Administrator@test.com
15 | #- DefaultAdminPassword=Secret1*
16 | healthcheck:
17 | test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/health"]
18 | interval: 30s
19 | timeout: 5s
20 | retries: 5
21 | start_period: 30s
22 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | services:
4 |
5 | unicornstore:
6 | build:
7 | context: ./
8 | dockerfile: UnicornStore/Dockerfile
9 | ports:
10 | - 8080:80
11 |
--------------------------------------------------------------------------------
/static/640px-Amazon_Web_Services_Logo.svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/640px-Amazon_Web_Services_Logo.svg.png
--------------------------------------------------------------------------------
/static/AWS-Logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/Amazon_Web_Services_Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
39 |
--------------------------------------------------------------------------------
/static/images/secrets/cloud9-dotnet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/cloud9-dotnet.png
--------------------------------------------------------------------------------
/static/images/secrets/connectionstring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/connectionstring.png
--------------------------------------------------------------------------------
/static/images/secrets/csproj-usersecretsid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/csproj-usersecretsid.png
--------------------------------------------------------------------------------
/static/images/secrets/docker-compose-development.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/docker-compose-development.png
--------------------------------------------------------------------------------
/static/images/secrets/examplesecret.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/examplesecret.png
--------------------------------------------------------------------------------
/static/images/secrets/modernization-unicorn-store-task-definition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/modernization-unicorn-store-task-definition.png
--------------------------------------------------------------------------------
/static/images/secrets/prerequisites/bashrc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/prerequisites/bashrc.png
--------------------------------------------------------------------------------
/static/images/secrets/prerequisites/iam-1-create-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/prerequisites/iam-1-create-user.png
--------------------------------------------------------------------------------
/static/images/secrets/prerequisites/iam-2-attach-policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/prerequisites/iam-2-attach-policy.png
--------------------------------------------------------------------------------
/static/images/secrets/prerequisites/iam-3-create-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/prerequisites/iam-3-create-user.png
--------------------------------------------------------------------------------
/static/images/secrets/prerequisites/iam-4-save-url.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/prerequisites/iam-4-save-url.png
--------------------------------------------------------------------------------
/static/images/secrets/prerequisites/portal_buttons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/prerequisites/portal_buttons.png
--------------------------------------------------------------------------------
/static/images/secrets/prerequisites/portal_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/prerequisites/portal_login.png
--------------------------------------------------------------------------------
/static/images/secrets/program-createdefaultbuilder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/program-createdefaultbuilder.png
--------------------------------------------------------------------------------
/static/images/secrets/retrieveunicornsecret-policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/retrieveunicornsecret-policy.png
--------------------------------------------------------------------------------
/static/images/secrets/secret-appsettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/secret-appsettings.png
--------------------------------------------------------------------------------
/static/images/secrets/secret-final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/secret-final.png
--------------------------------------------------------------------------------
/static/images/secrets/secrets-manager-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/secrets-manager-architecture.png
--------------------------------------------------------------------------------
/static/images/secrets/startup-configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/startup-configuration.png
--------------------------------------------------------------------------------
/static/images/secrets/unicornstore-docker-images.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/unicornstore-docker-images.png
--------------------------------------------------------------------------------
/static/images/secrets/unicornstore-ecr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/unicornstore-ecr.png
--------------------------------------------------------------------------------
/static/images/secrets/unicornstore-prod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/secrets/unicornstore-prod.png
--------------------------------------------------------------------------------
/static/images/unicornstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/modernization-unicorn-store/ee4470ddee7503eda40ed7af82c34d0a61ac1d9d/static/images/unicornstore.png
--------------------------------------------------------------------------------