6 Commits
44 changed files with 406 additions and 300 deletions

No files matched your search

+17
View File
@@ -179,3 +179,20 @@ and contact details was excluded from the repository; free-checkout and domain
management screenshots provide non-sensitive evidence instead. Public HTTPS, management screenshots provide non-sensitive evidence instead. Public HTTPS,
renewal tests, external exposure, persistence replacement, Payton's PR review, renewal tests, external exposure, persistence replacement, Payton's PR review,
merge/closure, remaining issues/README, and final compilation are not claimed. merge/closure, remaining issues/README, and final compilation are not claimed.
\subsection{Record 10 --- HTTPS, Firewall, Persistence, and Report Completion}
\textbf{Date:} October 13--15, 2026.\\
\textbf{Tool and model:} Anthropic Claude (Opus 5.5).\\
\textbf{Task:} Plan Payton's remaining Chapter~2 work; guide security-group
changes, Caddy and Compose configuration, the renewal, persistence, and
external exposure tests; draft README edits, the architecture diagram,
and this chapter's completed sections.\\
\textbf{Human actions:} Payton ran every console action and command, captured
all screenshots, chose Caddy, wrote and edited the issues, opened and merged
PR 7, and reviewed PR 2. Victor reviewed PR 7. The assistant viewed the AWS
console read-only; it made no AWS, DNS, or Gitea changes.\\
\textbf{Output used:} Configuration (Caddyfile, Compose additions), README
edits, TikZ diagram, and chapter text based on Payton's screenshots and
command output.\\
\textbf{Evidence handling and limits:} Values in this chapter come from the
saved screenshots. Team IP addresses are redacted in the text.
+4
View File
@@ -2,7 +2,9 @@
\newacronym{ami}{AMI}{Amazon Machine Image} \newacronym{ami}{AMI}{Amazon Machine Image}
\newacronym{iam}{IAM}{identity and access management} \newacronym{iam}{IAM}{identity and access management}
\newacronym{mfa}{MFA}{multi-factor authentication} \newacronym{mfa}{MFA}{multi-factor authentication}
\newacronym{acme}{ACME}{Automatic Certificate Management Environment}
\newacronym{ssh}{SSH}{secure shell} \newacronym{ssh}{SSH}{secure shell}
\newacronym{tls}{TLS}{Transport Layer Security}
\newacronym{wip}{WIP}{work in progress} \newacronym{wip}{WIP}{work in progress}
\newacronym{wsl}{WSL}{Windows Subsystem for Linux} \newacronym{wsl}{WSL}{Windows Subsystem for Linux}
\newglossaryentry{evidence}{name={evidence},description={A retained record of an \newglossaryentry{evidence}{name={evidence},description={A retained record of an
@@ -14,6 +16,8 @@
\newglossaryentry{texlive}{name={TeX Live},description={A cross-platform \newglossaryentry{texlive}{name={TeX Live},description={A cross-platform
distribution of the \TeX\ engines, \LaTeX\ packages, fonts, and tools; the distribution of the \TeX\ engines, \LaTeX\ packages, fonts, and tools; the
distribution Overleaf compiles with}} distribution Overleaf compiles with}}
\newglossaryentry{caddy}{name={Caddy},description={A web server and reverse
proxy that obtains and renews TLS certificates automatically; the team's HTTPS endpoint for Gitea}}
\newglossaryentry{sqlite}{name={SQLite},description={An embedded database stored \newglossaryentry{sqlite}{name={SQLite},description={An embedded database stored
in a local file; Gitea's selected database for the initial two-person deployment}} in a local file; Gitea's selected database for the initial two-person deployment}}
\newglossaryentry{elasticip}{name={Elastic IP},description={An allocated AWS \newglossaryentry{elasticip}{name={Elastic IP},description={An allocated AWS
+2
View File
@@ -117,6 +117,8 @@ curl -I http://127.0.0.1
The create command assumes the container name is unused. The restart policy The create command assumes the container name is unused. The restart policy
allows the running service to resume after a host restart. The application binds allows the running service to resume after a host restart. The application binds
to all container interfaces, and the AWS security group permits public HTTP. to all container interfaces, and the AWS security group permits public HTTP.
Since October 15, 2026, the container no longer publishes port 80; Caddy serves
it by IP over the private Docker network (\ChapterRef{GiteaFoundation}).
\section{Verification Evidence} \section{Verification Evidence}
The terminal screenshot shows container \texttt{70ed56f7fae8} running, the The terminal screenshot shows container \texttt{70ed56f7fae8} running, the
+310 -291
View File
@@ -1,25 +1,19 @@
\chapter{Startup Foundation with Gitea} \chapter{Startup Foundation with Gitea}
\ChapterLabel{GiteaFoundation} \ChapterLabel{GiteaFoundation}
\index{Gitea}\index{SQLite}\index{Docker Compose}\index{Elastic IP}\index{SSH tunnel} \index{Gitea}\index{SQLite}\index{Docker Compose}\index{Elastic IP}
This chapter records the initial Inviscid work hub on the existing Amazon EC2 This chapter records the Inviscid work hub on the existing Amazon EC2 server.
server. AWS is used in place of the assignment's DigitalOcean deployment. AWS is used in place of the assignment's DigitalOcean deployment. Victor
The dated screenshots record completed work; pending items below are not completed the domain, DNS, Gitea deployment, and account setup; Payton completed
represented as passed. This draft covers Victor's deployment and access setup. HTTPS, the firewall, persistence testing, and the remaining workflow evidence.
\section{Student Pack Domain and DNS} \section{Student Pack Domain and DNS}
Victor submitted current school-enrollment evidence to GitHub Education on After GitHub Education approved Victor's Student Developer Pack application on
October 12, 2026. Academic status was approved that day, while partner benefits October 12, partner benefits activated on October 15. Victor then redeemed
remained in ``Awaiting Benefits'' status with a stated 72-hour activation delay Name.com's Student Pack offer. It covered selected extensions rather than the
(Figure~\ref{Figure:GiteaBenefits}). On October 15 the benefits became available, preferred \texttt{inviscid.net}, so he registered \texttt{inviscid.dev} for one
and Victor redeemed Name.com's GitHub Student Developer Pack offer. year. No registrar-sold SSL product was needed, because Caddy obtains the
certificate (Table~\ref{Table:GiteaCertOptions}).
The offer covered selected extensions rather than the preferred
\texttt{inviscid.net}. Victor selected \texttt{inviscid.dev}, registered it for
one year, and supplied the completed order and domain-management evidence.
The checkout total was \$0.00, with the free Domain Safe option; Domain Safe Pro
and optional hosting/email upgrades were not selected. No registrar-provided
SSL product is needed for the planned Caddy deployment.
\begin{tabularx}{\linewidth}{L{3.8cm}Y} \begin{tabularx}{\linewidth}{L{3.8cm}Y}
\caption{Student Domain and DNS Record\label{Table:GiteaDomain}}\\ \caption{Student Domain and DNS Record\label{Table:GiteaDomain}}\\
@@ -28,69 +22,37 @@ Provider and eligibility & Name.com, redeemed through the activated GitHub Stude
Registered domain & \texttt{inviscid.dev}\\ Registered domain & \texttt{inviscid.dev}\\
Registration date / term & October 15, 2026 / one year\\ Registration date / term & October 15, 2026 / one year\\
Initial charge & \$0.00\\ Initial charge & \$0.00\\
Displayed renewal price & \$22.99; verify current price before renewal\\ Displayed renewal price & \$22.99; verify the current price before renewal\\
Displayed renewal date & October 15, 2027\\ Renewal date & October 15, 2027\\
Account owner / renewal responsibility & Victor; keep payment/contact details current and track renewal before the anniversary\\ Renewal controls & Automatic renewal ON; WHOIS privacy ON; transfer lock enabled; Victor tracks renewal and keeps payment details current\\
Renewal and protection settings & Automatic renewal ON; WHOIS privacy ON; transfer lock enabled\\ DNS record & A, host \texttt{git}, answer \texttt{100.49.80.94} (Elastic IP), TTL 300 seconds\\
Gitea hostname & \texttt{git.inviscid.dev}\\ Public service URL & \url{https://git.inviscid.dev/}\\
DNS hosting & Name.com's DNS management; exact authoritative nameserver values not yet captured\\
DNS record & A, host \texttt{git}, answer \texttt{100.49.80.94}, TTL 300 seconds\\
Intended public service URL & \url{https://git.inviscid.dev/}; DNS verified, HTTPS still pending in this record\\
\bottomrule \bottomrule
\end{tabularx} \end{tabularx}
Victor manages registration and renewal; the registrar's automatic-renewal
setting and October 2027 anniversary provide the recorded renewal controls.
Domain registration and DNS are independent of the class AWS account hosting
the application. Prices can change, so the table records the displayed price,
not a guarantee of the eventual renewal charge.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/student-pack-approved-awaiting-benefits.png}
\caption{October 12: academic approval preceded partner-benefit activation. The delay was resolved before domain registration on October 15.}
\label{Figure:GiteaBenefits}
\end{figure}
\begin{figure}[htbp]
\centering
\includegraphics[width=0.62\linewidth,height=0.60\textheight,keepaspectratio]{figures/gitea/student-pack-domain-offer.png}
\caption{October 15: Name.com Student Pack offer for selected domain extensions.}
\label{Figure:GiteaDomainOffer}
\end{figure}
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/domain-free-checkout.png} \includegraphics[width=\linewidth]{figures/gitea/domain-free-checkout.png}
\caption{October 15: one-year inviscid.dev registration at a checkout total of zero, with paid Domain Safe Pro unselected.} \caption{October 15: one-year inviscid.dev registration at a checkout total of zero.}
\label{Figure:GiteaDomainCheckout} \label{Figure:GiteaDomainCheckout}
\end{figure} \end{figure}
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/domain-renewal-details.png} \includegraphics[width=\linewidth]{figures/gitea/domain-renewal-details.png}
\caption{October 15: registered domain, October 15, 2027 renewal date, displayed renewal price, auto-renewal, privacy, and transfer-lock settings.} \caption{October 15: registered domain, renewal date and price, auto-renewal, privacy, and transfer-lock settings.}
\label{Figure:GiteaDomainRenewal} \label{Figure:GiteaDomainRenewal}
\end{figure} \end{figure}
In Name.com's Manage DNS page, Victor added an A record with host Victor added the A record in Name.com's DNS manager
\texttt{git}; the interface appended \texttt{.inviscid.dev}. The answer is the (Figure~\ref{Figure:GiteaDNSRecord}) and verified it from outside EC2 at 19:17 EDT
EC2 Elastic IP rather than the old auto-assigned address. The saved record on October 15: both Cloudflare (1.1.1.1) and Google (8.8.8.8) resolvers returned
and creation date are visible in Figure~\ref{Figure:GiteaDNSRecord}. \texttt{100.49.80.94} (Figure~\ref{Figure:GiteaDNSLookup}).
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/dns-a-record.png} \includegraphics[width=\linewidth]{figures/gitea/dns-a-record.png}
\caption{October 15: saved A record for git.inviscid.dev pointing to 100.49.80.94 with a 300-second TTL.} \caption{October 15: saved A record for git.inviscid.dev pointing to 100.49.80.94 with a 300-second TTL.}
\label{Figure:GiteaDNSRecord} \label{Figure:GiteaDNSRecord}
\end{figure} \end{figure}
Victor independently verified the record from Windows PowerShell, outside
EC2, using two public DNS resolvers:
\begin{minted}{powershell}
Get-Date -Format o
Resolve-DnsName git.inviscid.dev -Type A -Server 1.1.1.1
Resolve-DnsName git.inviscid.dev -Type A -Server 8.8.8.8
\end{minted}
The supplied timestamp is October 15, 2026 at 19:17:31, UTC offset -04:00.
Both resolvers returned an A answer of \texttt{100.49.80.94}, TTL 300.
This verifies public name resolution, not application availability or TLS.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/dns-independent-lookups.png} \includegraphics[width=\linewidth]{figures/gitea/dns-independent-lookups.png}
@@ -99,18 +61,19 @@ This verifies public name resolution, not application availability or TLS.
\end{figure} \end{figure}
\section{Pinned Gitea Deployment} \section{Pinned Gitea Deployment}
The existing \texttt{ssw590-group-web} instance runs Ubuntu 26.04 LTS on a Gitea runs on the existing \texttt{ssw590-group-web} instance (Ubuntu 26.04 LTS,
\texttt{t3.micro} in \texttt{us-east-1}. The initial resource check recorded \texttt{t3.micro}, \texttt{us-east-1}), whose Elastic IP keeps the DNS record
908 MiB total memory, 468 MiB available memory, and 3.1 GiB available root disk. valid across restarts. Victor deployed Gitea privately on October 12. On
Docker Compose reported version 5.6.0. The existing Color Buttons container October 15, Payton added Caddy as the HTTPS endpoint and recreated the existing
continued to serve host port 80 while Gitea was initialized privately. Color Buttons container without a published port on \texttt{inviscid\_services},
so that Caddy could take port 80; the recreate command is in the deployment
README.
\begin{tabularx}{\linewidth}{L{3.5cm}Y} \begin{tabularx}{\linewidth}{L{3.5cm}Y}
\caption{Recorded Gitea Deployment\label{Table:GiteaDeployment}}\\ \caption{Recorded Gitea Deployment\label{Table:GiteaDeployment}}\\
\toprule \textbf{Setting} & \textbf{Value}\\ \midrule \toprule \textbf{Setting} & \textbf{Value}\\ \midrule
Instance & \texttt{i-025a57432604c8d4b}, \texttt{ssw590-group-web}\\ Instance & \texttt{i-025a57432604c8d4b}, \texttt{ssw590-group-web}\\
Current public address & Elastic IP \texttt{100.49.80.94}\\ Public / private address & Elastic IP \texttt{100.49.80.94} / \texttt{172.31.19.91}\\
Private address & \texttt{172.31.19.91}\\
Official image & \texttt{docker.gitea.com/gitea:28.1.0}\\ Official image & \texttt{docker.gitea.com/gitea:28.1.0}\\
Container & \texttt{inviscid-gitea-1}\\ Container & \texttt{inviscid-gitea-1}\\
Remote working directory & \path{/home/ubuntu/gitea}\\ Remote working directory & \path{/home/ubuntu/gitea}\\
@@ -118,60 +81,39 @@ Persistent volume & \texttt{inviscid\_gitea\_data}, mounted at \path{/data}\\
Docker network & \texttt{inviscid\_services}\\ Docker network & \texttt{inviscid\_services}\\
Restart policy & \texttt{unless-stopped}\\ Restart policy & \texttt{unless-stopped}\\
Published backend & \texttt{127.0.0.1:3000:3000}; no public backend mapping\\ Published backend & \texttt{127.0.0.1:3000:3000}; no public backend mapping\\
Temporary public URL setting & \url{http://localhost:3000/}; SSH-tunnel access only\\ Public URL setting & \url{https://git.inviscid.dev/}\\
TLS proxy & \texttt{caddy:2.10.2}, container \texttt{inviscid-caddy-1}; the only service publishing host ports 80 and 443\\
\bottomrule \bottomrule
\end{tabularx} \end{tabularx}
The official Docker installation guide supports persistent application storage Both images are pinned to version tags rather than \texttt{latest}. The recorded
and SQLite initialization. We pinned a version rather than using a floating Gitea repository digest is:
\texttt{latest} tag. The recorded repository digest is:
\begin{minted}[fontsize=\scriptsize]{text} \begin{minted}[fontsize=\scriptsize]{text}
docker.gitea.com/gitea@sha256:f505a0d3a41323b786e05942fc7a4075e07ae7c169fd9485649aa55ee543a21a docker.gitea.com/gitea@sha256:f505a0d3a41323b786e05942fc7a4075e07ae7c169fd9485649aa55ee543a21a
\end{minted} \end{minted}
The non-secret configuration is maintained in The non-secret configuration is maintained in
\href{https://github.com/byterand/SSW590-DevOps/tree/main/docker/gitea}{the team's versioned deployment directory}. \href{https://git.inviscid.dev/Inviscid/SSW590-DevOps/src/branch/main/docker/gitea}{the team's versioned deployment directory}
The following file is the initial localhost-only configuration. Its URL settings in the Gitea repository. The deployed Compose file and Caddyfile follow.
must be updated for the registered HTTPS hostname before final submission.
\inputminted{yaml}{docker/gitea/compose.yaml} \inputminted{yaml}{docker/gitea/compose.yaml}
\inputminted{text}{docker/gitea/Caddyfile}
Victor copied the directory from the Windows workstation to EC2 using To repeat the installation, copy the directory to the server and run, from it:
\texttt{scp}. In the remote directory, he validated, pulled, and started it:
\begin{minted}{bash} \begin{minted}{bash}
cd ~/gitea cd ~/gitea
sudo docker compose config --quiet sudo docker compose config --quiet
sudo docker compose pull sudo docker compose pull
sudo docker compose up -d sudo docker compose up -d
sudo docker compose ps sudo docker compose ps
sudo docker compose logs --tail=40 gitea
\end{minted} \end{minted}
Validation returned without an error, the pinned image pulled, and Compose Validation returned without an error and the pinned image pulled
created the volume, network, and container. Startup logs showed Gitea 28.1.0 (Figure~\ref{Figure:GiteaPull}).
listening internally on port 3000 and preparing its installation page.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/compose-validation-pull.png} \includegraphics[width=\linewidth]{figures/gitea/compose-validation-pull.png}
\caption{October 12: successful Compose validation and pull of the pinned official image.} \caption{October 12: successful Compose validation and pull of the pinned official image.}
\label{Figure:GiteaPull} \label{Figure:GiteaPull}
\end{figure} \end{figure}
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/container-startup.png}
\caption{October 12: container startup and localhost port mapping. The image exposes an internal SSH port, but no host SSH mapping was published for Gitea.}
\label{Figure:GiteaStartup}
\end{figure}
Victor associated the Elastic IP with the same instance, replacing the earlier
\texttt{54.221.175.174} address. The containers remained running. SSH and tunnel
commands were updated to use the new address; the data and private address did
not change.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/elastic-ip-association.png}
\caption{October 12: Elastic IP 100.49.80.94 associated with the existing instance.}
\label{Figure:GiteaElasticIP}
\end{figure}
\section{Database and Persistence} \section{Database and Persistence}
SQLite was selected for the small two-person deployment. It requires no SQLite was selected for the small two-person deployment. It requires no
@@ -179,260 +121,337 @@ separate database service or network port. The database is at
\path{/data/gitea/gitea.db}, within the same persistent named volume that holds \path{/data/gitea/gitea.db}, within the same persistent named volume that holds
application configuration and repository data. The volume exists outside the application configuration and repository data. The volume exists outside the
disposable container layer. Generated configuration, database contents, and disposable container layer. Generated configuration, database contents, and
secrets are not stored in the Git repository. secrets are not stored in the Git repository. SQLite suits this team's size but
may not fit a larger, higher-concurrency deployment.
The following checks were executed on October 12: \subsection{Container-Replacement Test}
On October 15 at 21:34 EDT, Payton replaced only the Gitea container, using
repository \texttt{Inviscid/SSW590-DevOps} and issue \#3 as the recorded data.
Volumes were not removed:
\begin{minted}{bash} \begin{minted}{bash}
sudo docker inspect inviscid-gitea-1 --format '{{range .Mounts}}{{println .Type .Name .Destination}}{{end}}' sudo docker compose up -d --force-recreate --no-deps gitea
sudo docker compose exec -T gitea ls -lh /data/gitea/gitea.db
sudo docker stats --no-stream
free -h
df -h /
\end{minted} \end{minted}
The mount check showed \texttt{inviscid\_gitea\_data} at \path{/data}; the The container ID changed from \texttt{915c6b6d88d2} to \texttt{82ca41970439},
SQLite file existed and was approximately 2.1 MiB. Gitea used approximately confirming that the container and its writable layer were replaced. The same
187 MiB RAM in that snapshot, with 367 MiB system memory and 2.8 GiB disk volume remained mounted at \path{/data}, the 2.2 MiB database file was still
available. These are single observations, not load-test results. present (Figure~\ref{Figure:GiteaPersistence}), and the repository and issue \#3
were unchanged and usable (Figure~\ref{Figure:GiteaPersistenceIssue}). The data
survived because Compose reattached the named volume. This is a persistence
test, not a backup; see the Architecture and Operation section.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/storage-resources.png} \includegraphics[width=\linewidth]{figures/gitea/persistencetest.png}
\caption{October 12: named-volume mount, database file, and resource checks. These verify storage configuration, not survival of a container replacement.} \caption{October 15: container ID, mount, replacement command, new container ID, and database file.}
\label{Figure:GiteaStorage} \label{Figure:GiteaPersistence}
\end{figure}
\begin{figure}[htbp]
\centering
\begin{minipage}{0.49\linewidth}\centering
\includegraphics[width=\linewidth]{figures/gitea/beforeissue.png}\\ \small Before
\end{minipage}\hfill
\begin{minipage}{0.49\linewidth}\centering
\includegraphics[width=\linewidth]{figures/gitea/afterissue.png}\\ \small After
\end{minipage}
\caption{October 15: issue \#3 before and after the Gitea container replacement.}
\label{Figure:GiteaPersistenceIssue}
\end{figure} \end{figure}
\textbf{Pending---Payton:} record a repository and issue, replace only the
Gitea container without deleting its volume, and show that both remain usable.
Include identifiers, exact command, before/after evidence, and conclusion.
This required test is distinct from the optional backup/restore exercise.
\section{HTTPS and Certificate Renewal} \section{HTTPS and Certificate Renewal}
\textbf{Pending---Payton:} compare two certificate options and justify one; \index{Caddy}\index{Let's Encrypt}
configure Caddy using \texttt{git.inviscid.dev}, persistent certificate state, and \subsection{Certificate Options}
private forwarding to \texttt{gitea:3000}; update Gitea's domain and public URL; Both options below use Let's Encrypt, a free certificate authority that issues
record issuer, expiry, redirect, renewal automation, and a successful renewal 90-day certificates through \gls{acme}. A paid or registrar-sold certificate was
test or dry run. The existing website's host-port-80 mapping must be resolved rejected because it needs manual installation and renewal.
before Caddy publishes that port. Public HTTPS is not claimed in this draft.
Initial access used an SSH tunnel from the workstation: \begin{tabularx}{\linewidth}{L{2.8cm}YY}
\begin{minted}{bash} \caption{Certificate Options\label{Table:GiteaCertOptions}}\\
ssh -i /path/to/your-private-key -o ExitOnForwardFailure=yes -N -L 127.0.0.1:3000:127.0.0.1:3000 ubuntu@100.49.80.94 \toprule \textbf{Criterion} & \textbf{Caddy automatic HTTPS (selected)} & \textbf{certbot with nginx}\\ \midrule
\end{minted} Issuance and renewal & Built into the proxy; no extra job & Separate certbot client and renewal timer\\
The browser opened \url{http://localhost:3000/}; transport between workstation Configuration & One Caddyfile site block; HTTP-to-HTTPS redirect is automatic & nginx server blocks plus certbot plugin\\
and server was encrypted by SSH, but this is not hostname-based HTTPS and does Containers & One proxy container & Proxy plus certbot, or certbot on the host\\
not satisfy the final certificate requirement. Each workstation needs its own Renewal test & No dry-run command; forced reissue used instead & \texttt{certbot renew --dry-run}\\
tunnel. The \texttt{-N} option keeps the connection open without a remote shell.
\section{Firewall and External Exposure}
The attached AWS security group is \texttt{ssw590-web-sg}, identifier
\texttt{sg-0a9eb6dcfc906e78b}. The October 15 screenshot records these IPv4 rules:
\begin{tabularx}{\linewidth}{L{2cm}L{4cm}Y}
\toprule \textbf{TCP port} & \textbf{Source} & \textbf{Purpose}\\ \midrule
22 & \texttt{100.8.28.98/32} & Victor's home SSH access\\
22 & \texttt{155.246.151.38/32} & Victor's campus source; connection still timed out during the reported attempt\\
22 & \texttt{108.35.228.38/32} & Payton's administrative SSH access\\
80 & \texttt{0.0.0.0/0} & Existing HTTP website; final proxy redirect/validation behavior pending\\
443 & \texttt{0.0.0.0/0} & Permitted for planned Caddy HTTPS; opening a rule does not verify a live HTTPS endpoint\\
\bottomrule \bottomrule
\end{tabularx} \end{tabularx}
No public IPv6 address was shown in the supplied EC2 summary. The provided
inbound-rule screenshot contains IPv4 rules only. Outbound rules, any other \Gls{caddy} was selected for fewer moving parts and secure defaults. Its main
attached security groups, and final external exposure require Payton's review. drawback is the lack of a dry-run renewal command.
\subsection{TLS Termination and Evidence}
\gls{tls} terminates at Caddy. Caddy forwards plain HTTP to \texttt{gitea:3000}
over the private Docker network \texttt{inviscid\_services}; that traffic never
leaves the instance. Port 80 is open for HTTP-to-HTTPS redirects and ACME
validation.
Caddy obtained the Let's Encrypt certificate for \texttt{git.inviscid.dev} at
19:52 EDT on October 15; the current certificate is valid until January 13, 2027
(90 days). From Payton's laptop, \texttt{http://git.inviscid.dev} returned
\texttt{308 Permanent Redirect} to \url{https://git.inviscid.dev/}, and the HTTPS
URL returned HTTP/2 200 (Figure~\ref{Figure:GiteaExternalTests}). The
\texttt{.dev} top-level domain is on browsers' HSTS preload list, so browsers
never request plain HTTP for this host; the redirect matters for non-browser
clients and was tested with \texttt{curl}.
\subsection{Renewal Automation and Test}
Caddy renews managed certificates automatically in the background, by default
when about one third of the lifetime remains (around mid-December 2026 for
this certificate). No cron job or manual command is required. Certificate and
ACME account state persist in \texttt{inviscid\_caddy\_data}, so replacing the
Caddy container does not force reissuance.
Because Caddy has no dry-run command, Payton forced a reissue by deleting the
stored certificate and restarting Caddy. This exercises the same unattended
ACME path that renewal uses:
\begin{minted}{bash}
sudo docker compose exec caddy rm -rf \
/data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/git.inviscid.dev
sudo docker compose restart caddy
sudo docker compose logs --since 1m caddy | grep -i obtained
\end{minted}
Caddy logged \texttt{certificate obtained successfully} without manual
intervention, and the serial changed from \texttt{06ADFF\ldots F6A7} to
\texttt{06CA6A\ldots 034F} with a new validity window
(Figure~\ref{Figure:GiteaRenewal}). This was a forced reissue, not an observed
natural renewal, and was run once to stay within Let's Encrypt's
duplicate-certificate limit.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/caddy.png}
\caption{October 15: certificate serial and validity before and after the forced reissue, with Caddy's log entry.}
\label{Figure:GiteaRenewal}
\end{figure}
\section{Firewall and External Exposure}
The only security group attached to the instance is \texttt{ssw590-web-sg}
(\texttt{sg-0a9eb6dcfc906e78b}); Figure~\ref{Figure:GiteaArchitecture} shows the
resulting network paths. Team source addresses are partially redacted.
\begin{tabularx}{\linewidth}{L{1.6cm}L{3.4cm}Y}
\toprule \textbf{Inbound TCP} & \textbf{Source} & \textbf{Purpose}\\ \midrule
443 & \texttt{0.0.0.0/0} & HTTPS to Caddy for \texttt{git.inviscid.dev}\\
80 & \texttt{0.0.0.0/0} & HTTP-to-HTTPS redirect, ACME validation, Color Buttons by IP\\
22 & \texttt{100.8.x.x/32} & Victor, home\\
22 & \texttt{155.246.x.x/32} & Victor, campus\\
22 & \texttt{108.35.x.x/32} & Payton\\
\bottomrule
\end{tabularx}
Outbound allows all traffic to \texttt{0.0.0.0/0}. The server needs outbound
access for package and image downloads, DNS, time synchronization, and Let's
Encrypt; the group is stateful, so replies to allowed inbound connections are
permitted. The instance has no IPv6 address, so there are no IPv6 rules. Docker
also binds ports 80 and 443 on IPv6, but they are unreachable without an IPv6
address. The subnet's network ACL (\texttt{acl-06bdf6081fd8b9d5d}) is the default
allow-all in both directions, so the security group is the effective filter
(Figures~\ref{Figure:GiteaFirewall} and~\ref{Figure:GiteaNACL}).
The Color Buttons app from an earlier assignment is still served over plain
HTTP by IP, which does not meet the hostname-based HTTPS rule. It is kept
because the instructor expects the instance and this app to stay reachable; it
no longer publishes its own port, and giving it an HTTPS hostname is future work.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/security-group-team-access.png} \includegraphics[width=\linewidth]{figures/gitea/security.png}
\caption{October 15: saved security-group rules, including Payton's source-specific SSH rule. This is configuration evidence, not an external port scan.} \caption{October 15: the single attached security group with final inbound and outbound rules.}
\label{Figure:GiteaFirewall} \label{Figure:GiteaFirewall}
\end{figure} \end{figure}
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/subnets.png}
\caption{October 15: default network ACL on the instance's subnet.}
\label{Figure:GiteaNACL}
\end{figure}
During an October 13 campus connection attempt, SSH timed out. Victor's public \subsection{Host and Container Exposure}
IP had changed from the home address; a campus-specific rule was added, but Docker-published ports can bypass host firewall (ufw) rules, so the security
that attempt still failed. Campus filtering was considered but not established. group is the public boundary. Figure~\ref{Figure:GiteaPorts} shows that only
On October 15 Victor reported successful SSH access; the exact network for \texttt{docker-proxy} (Caddy) listens on 80 and 443 and \texttt{sshd} on 22.
that successful session was not recorded. Rules should track current approved Gitea's port 3000 listens on \texttt{127.0.0.1} only, and the Color Buttons
source addresses rather than permitting public administrative SSH. container and Caddy's admin port 2019 have no host mapping.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/ports.png}
\caption{October 15: Docker port mappings and listening sockets on EC2.}
\label{Figure:GiteaPorts}
\end{figure}
\textbf{Pending---Payton:} externally test 443, 80, restricted administrative \subsection{External Exposure Test}
SSH, backend 3000, and database exposure; record source, time, expected and Tests ran from Payton's laptop on October 15, outside EC2, using
actual results, and corrections. Document outbound rules and final Docker \texttt{nc -vz -w 5} and \texttt{curl}:
publishing. HTTPS encrypts an allowed connection; it does not close unrelated \begin{tabularx}{\linewidth}{L{1.5cm}L{3.3cm}L{2.6cm}Y}
ports. Local HTTP 200 and loopback mappings do not independently prove public \toprule \textbf{Port} & \textbf{Source (EDT)} & \textbf{Expected} & \textbf{Actual}\\ \midrule
firewall behavior. 443 & \texttt{108.35.x.x}, 21:53 & open & connected; HTTP/2 200\\
80 & \texttt{108.35.x.x}, 21:53 & open, redirect & connected; 308 to HTTPS; IP returns 200 (Color Buttons)\\
22 & \texttt{108.35.x.x}, 21:53 & open (allowed) & connected\\
3000 & \texttt{108.35.x.x}, 21:53 & blocked & timed out\\
222 & \texttt{108.35.x.x}, 21:53 & blocked & timed out\\
2019 & \texttt{108.35.x.x}, 21:53 & blocked & timed out\\
22 & \texttt{174.206.x.x} (phone hotspot), 21:53--21:56 & blocked & timed out\\
\bottomrule
\end{tabularx}
All results matched expectations, so no corrections were needed. The laptop was
switched to the hotspot before the final SSH test; \texttt{checkip} afterwards
confirmed the hotspot address. Timeouts, rather than refusals, show that the
security group drops the traffic before it reaches the instance.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/laptop.png}
\caption{October 15: external port, redirect, and HTTPS tests from an allowed source and from a non-allowed hotspot.}
\label{Figure:GiteaExternalTests}
\end{figure}
HTTPS encrypts connections that the firewall admits; it does not close any
port. Without the security group, Gitea's port, SSH, or any accidentally
published container port could be reached directly.
\section{Startup Team Workflow} \section{Startup Team Workflow}
Victor initialized Gitea with site title \texttt{Inviscid}, created the Victor initialized Gitea with site title \texttt{Inviscid}, created the
\texttt{victor} administrator, and disabled public self-registration. A separate \texttt{victor} site administrator, and disabled public self-registration. He
\texttt{payton} regular account was created with a temporary password and created a separate regular \texttt{payton} account with a temporary password,
mandatory password change. A shared Gitea account is not used. which Payton changed after first login; no shared Gitea account is used.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/member-accounts.png}
\caption{October 12: separate victor and payton accounts; victor has the site-administrator role. This does not yet establish organization permissions or successful Payton login.}
\label{Figure:GiteaMembers}
\end{figure}
On October 15 Victor added Payton's public SSH key to the remote Ubuntu
account's authorized keys, preserving the existing key and using directory/file
permissions 700/600. His source IP was allowed separately. Victor subsequently
reported that Payton connected over SSH successfully and subsequently confirmed
that his Gitea account worked. His password-change completion was not separately
captured. Both currently use the Linux \texttt{ubuntu} account with separate
keys, so Linux account-level attribution remains shared.
\subsection{Organization, Repository, and Roles} \subsection{Organization, Repository, and Roles}
Victor took responsibility for the Gitea organization/repository work while Victor created organization \texttt{Inviscid} and added \texttt{payton} to its
Payton focused on the other infrastructure tasks. Victor created organization Owners team, so both members administer the organization and its repositories
\texttt{Inviscid} and added \texttt{payton} to its Owners team. Both (Figure~\ref{Figure:GiteaOwners}). Victor leads domain, DNS, and deployment
\texttt{victor} and \texttt{payton} therefore administer the organization and work; Payton leads HTTPS, firewall, persistence, and repository workflow.
its repositories; Payton's account is not a site-wide administrator. Victor then pushed the existing project history to the product repository,
\texttt{Inviscid/SSW590-DevOps}. Its root \texttt{README.md}, added in PR 7,
describes the product, repository layout, team roles, and contribution workflow
(Figure~\ref{Figure:GiteaReadme}). The organization and repository are at
\url{https://git.inviscid.dev/Inviscid} and
\url{https://git.inviscid.dev/Inviscid/SSW590-DevOps}.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/organization-owners.png} \includegraphics[width=\linewidth]{figures/gitea/organization-owners.png}
\caption{October 15: victor and payton are members of the Inviscid Owners team.} \caption{October 15: victor and payton are members of the Inviscid Owners team.}
\label{Figure:GiteaOwners} \label{Figure:GiteaOwners}
\end{figure} \end{figure}
Victor created \texttt{Inviscid/SSW590-DevOps} without initializing an unrelated
README commit, then pushed the existing project's \texttt{main} history.
The repository page showed 13 commits, including the initial Gitea deployment
commit \texttt{2807081}. GitHub remains the \texttt{origin} remote; the new
\texttt{gitea} remote points to the tunneled service. The observed commands were:
\begin{minted}{bash}
git remote add gitea http://localhost:3000/Inviscid/SSW590-DevOps.git
git push gitea main:main
\end{minted}
The intended final organization and repository links are
\url{https://git.inviscid.dev/Inviscid} and
\url{https://git.inviscid.dev/Inviscid/SSW590-DevOps}.
At the recorded checkpoint, access used the equivalent paths at
\url{http://localhost:3000/} through an SSH tunnel; the final HTTPS links are
not yet verified. These links must be checked before submission.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/repository-main-import.png} \includegraphics[width=\linewidth]{figures/gitea/readme.png}
\caption{October 15: existing project files and history imported into the organization repository on main.} \caption{October 15: product repository README.}
\label{Figure:GiteaRepository} \label{Figure:GiteaReadme}
\end{figure} \end{figure}
\subsection{Issue 1 and Documentation Pull Request 2} \subsection{Backlog}
Victor created issue \#1, ``Document the initial Gitea deployment and team The backlog contains five actionable issues, each with acceptance criteria:
access,'' assigned it to himself, and recorded acceptance criteria covering \begin{tabularx}{\linewidth}{L{0.6cm}YL{2.1cm}L{2.5cm}}
deployment facts, evidence, manual controls, pending work, and Overleaf review. \toprule \textbf{\#} & \textbf{Issue} & \textbf{Type} & \textbf{Status}\\ \midrule
Victor reported that he had uploaded the draft to Overleaf, compiled it, and 1 & Document the initial Gitea deployment and team access & Operations & Closed (PR 2)\\
visually reviewed it. This is author-reported validation of the earlier draft, 3 & Finalize README.md (product and deployment READMEs) & Product & Closed (PR 7)\\
not verification of later edits or the final combined submission. 4 & Enable HTTPS for git.inviscid.dev & Operations & Closed (PR 7)\\
5 & Configure EC2 firewall & Operations & Closed after tests\\
6 & Version the Color Buttons app in the repository & Product & Open\\
\bottomrule
\end{tabularx}
\subsection{Issue-Linked Pull Requests}
Gitea numbers issues and pull requests from one sequence, so the two PRs are
2 and 7.
\paragraph{PR 2 (Victor, reviewed by Payton).} Victor documented the initial
deployment on branch \texttt{codex/gitea-foundation-docs} and opened PR 2 with
\texttt{Closes \#1}, requesting Payton's review. Payton reviewed the changes,
approved, and merged the PR, which closed issue 1
(Figure~\ref{Figure:GiteaPR2}).
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/issue-1-documentation.png} \includegraphics[width=\linewidth]{figures/gitea/pr2merge.png}
\caption{October 15: open issue 1, acceptance criteria, and Victor's assignment. Checked criteria do not mean the issue has been merged or closed.} \caption{October 15: PR 2 approved by Payton and merged, closing issue 1.}
\label{Figure:GiteaIssueOne} \label{Figure:GiteaPR2}
\end{figure} \end{figure}
The changes were made on \texttt{codex/gitea-foundation-docs}, staged and \paragraph{PR 7 (Payton, reviewed by Victor).} Payton authored PR 7 from
checked, then committed as \texttt{da6a369} and pushed: \texttt{payton/caddy-https}. It adds the root README, the Caddy service and
\begin{minted}{bash} Caddyfile, the HTTPS URL settings, and the updated deployment README, with
git diff --cached --check \texttt{Closes \#3} and \texttt{Closes \#4}. Victor reviewed and approved it,
git diff --cached --stat and Payton merged it as commit \texttt{1d7c528795}, which closed both issues
git commit -m "Document initial Gitea deployment and team access; refs #1" (Figure~\ref{Figure:GiteaPR7}). The initial \texttt{main} push is not counted
git push -u gitea codex/gitea-foundation-docs as a reviewed merge.
\end{minted}
The commit recorded 12 changed files, 342 insertions, and 32 deletions.
Victor opened PR \#2 targeting \texttt{main}, included \texttt{Closes \#1}
in its description, and requested review from \texttt{payton}. Gitea uses a
shared numbering sequence for issues and PRs: PR \#2 is the first PR, not a
second completed workflow.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\includegraphics[width=\linewidth]{figures/gitea/pr-2-review-request.png} \includegraphics[width=\linewidth]{figures/gitea/mypr.png}
\caption{October 15: open PR 2 targets main, links issue 1, and requests Payton's review. Approval and merge are not shown.} \caption{October 15: PR 7 linked to issues 3 and 4, approved by Victor, and merged.}
\label{Figure:GiteaPROpen} \label{Figure:GiteaPR7}
\end{figure} \end{figure}
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/pr-2-chapter-diff.png}
\caption{October 15: Files Changed view exposes the documentation additions for peer review.}
\label{Figure:GiteaPRDiff}
\end{figure}
The intended final links are
\url{https://git.inviscid.dev/Inviscid/SSW590-DevOps/issues/1} and
\url{https://git.inviscid.dev/Inviscid/SSW590-DevOps/pulls/2}.
These were accessed through the SSH-tunnel hostname at this checkpoint.
\textbf{Pending---Payton and both authors:} Payton's formal review, any
requested corrections, merge, and issue closure; a separate issue-linked PR
authored by Payton and reviewed by Victor; README and at least five actionable
issues, including product and operations work. Only issue 1 and open PR 2 are
fully evidenced here. The dashboard issue was proposed but its creation was
not confirmed. Payton has taken ownership of the remaining Gitea tasks.
The initial main push is not counted as a reviewed PR.
\section{Architecture and Operation} \section{Architecture and Operation}
The current private initialization path is shown in Figure~\ref{Figure:GiteaArchitecture} shows the deployed request and data paths.
Figure~\ref{Figure:GiteaInitialPath}. Payton will add a final deployment diagram
showing browser HTTPS to Caddy and private forwarding to Gitea.
\begin{figure}[htbp] \begin{figure}[htbp]
\centering \centering
\fbox{\parbox{0.92\linewidth}{\centering \resizebox{\linewidth}{!}{\input{figures/devopsGiteaFoundation/giteaArchitecture}}
Workstation browser: localhost:3000\\ \caption{October 15: deployed architecture. The security group admits 443 and 80 from any source and 22 only from approved team addresses. Caddy is the only container publishing host ports; Gitea, the Color Buttons app, and both data volumes are private.}
$\downarrow$ local SSH tunnel\\ \label{Figure:GiteaArchitecture}
AWS security group: approved source to TCP 22\\
$\downarrow$ EC2: 100.49.80.94\\
EC2 loopback:3000 to Gitea container:3000\\
$\downarrow$ persistent named volume\\
\texttt{inviscid\_gitea\_data}: repositories, SQLite, configuration}}
\caption{Initial SSH-tunnel access path. Caddy and hostname-based HTTPS are not yet verified.}
\label{Figure:GiteaInitialPath}
\end{figure} \end{figure}
The completed local check \texttt{curl -I http://127.0.0.1:3000/} returned \paragraph{Where data lives.} Repositories, attachments, Gitea configuration,
HTTP 200. Logs, container status, and the image digest were collected without and the SQLite database (\path{/data/gitea/gitea.db}) live in the named volume
printing configuration secrets. \texttt{inviscid\_gitea\_data}. Certificates and the ACME account live in
\begin{figure}[htbp] \texttt{inviscid\_caddy\_data} (with \texttt{inviscid\_caddy\_config}). All
\centering three volumes are on the instance's EBS disk, outside the disposable container
\includegraphics[width=\linewidth]{figures/gitea/http-image-digest.png} layers. Deployment files are versioned in \path{docker/gitea/} of the repository.
\caption{October 12: local HTTP 200 and exact image digest.}
\label{Figure:GiteaHTTP}
\end{figure}
The following are operational commands, not additional claimed test results: \paragraph{Public and private interfaces.} Public: TCP 443 (HTTPS to Caddy) and
TCP 80 (redirect to HTTPS, ACME validation, and the Color Buttons app by IP),
both from any source. Restricted: TCP 22 (administrative SSH) from approved team
addresses only. Private: Gitea's port 3000 (reachable only on the Compose network
and EC2 loopback, for SSH-tunnel administration), the Color Buttons container's
port 3000, Caddy's admin port 2019, and the SQLite file, which has no network port.
\paragraph{Restart and troubleshooting.} From \path{~/gitea} on EC2:
\begin{minted}{bash} \begin{minted}{bash}
cd ~/gitea
sudo docker compose ps sudo docker compose ps
sudo docker compose logs --tail=40 caddy
sudo docker compose logs --tail=40 gitea sudo docker compose logs --tail=40 gitea
sudo docker compose restart gitea sudo docker compose restart gitea # or caddy; volumes are kept
\end{minted} \end{minted}
Closing a workstation tunnel does not stop Gitea on EC2. Reconnect the tunnel If \url{https://git.inviscid.dev} fails, check in order: (1) both services are
for browser access. Container replacement should retain the named volume; running; (2) the Caddy log shows no certificate or upstream errors; (3) the Gitea
never delete that volume as part of the persistence check. Before destructive log shows it listening on port 3000; (4) \texttt{curl -I http://127.0.0.1:3000/}
cleanup, retain needed data. Victor owns the class AWS environment; compute, on EC2 returns 200, which isolates Gitea from Caddy; (5) DNS still resolves to
the Elastic IP and the security group still admits 443.
\paragraph{Persistence test versus backup/restore.} The container-replacement
test shows that data survives when the Gitea \emph{container} is discarded,
because the named volume is reattached. It does not protect against loss of the
volume, the EBS disk, or the instance, and it creates no copy elsewhere. A
backup/restore test would copy the volume and database to separate storage and
prove recovery into a different instance; that optional exercise was not
performed.
\paragraph{Costs and cleanup.} Victor owns the class AWS environment; compute,
EBS, and public IPv4 are billable and covered by budget alerts rather than a EBS, and public IPv4 are billable and covered by budget alerts rather than a
spending cap. End-of-course cleanup must include the instance, unneeded storage, spending cap. End-of-course cleanup must include the instance, unneeded storage,
and release of the Elastic IP; stopping the instance alone is not full cleanup. and release of the Elastic IP; stopping the instance alone is not full cleanup.
The domain renews automatically on October 15, 2027 unless auto-renewal is
turned off.
\section{Manual Quality and Controls} \section{Manual Quality and Controls}
This revision adds the Gitea chapter, updates the Hosts inventory and Elastic IP This version updates the Hosts and Passwords tables, as recorded in the
cleanup notes, and adds Gitea account and SSH-key handling hints to the Passwords document history. No passwords, private keys, database contents, or generated
register. No passwords, private keys, database contents, or generated \texttt{app.ini} are included, and team IP addresses are redacted. Material AI
\texttt{app.ini} are included. Existing security policies are distinguished help is recorded in \ChapterRef{AIPromptLog}.
from observed deployment facts where necessary. Material AI help is recorded
in \ChapterRef{AIPromptLog}. The linked history entry identifies both table
updates. Optional backup/restore extra credit is outside this draft's scope.
\textbf{Pending before submission:} finish all pending sections, update the \paragraph{Failed attempts and corrective actions.}
final Compose/proxy configuration for \texttt{git.inviscid.dev}, complete the required team workflow, \begin{itemize}[nosep]
verify external exposure and renewal, compile the combined PDF, inspect its \item Victor's SSH from campus timed out after his public IP changed; a
pages and links, and submit only the PDF. No deadline time was supplied for campus-specific rule was added, and his later access succeeded.
the professor's extension to Thursday, October 15, 2026; check Canvas. \item Payton's first SSH login failed (\texttt{Permission denied (publickey)});
Victor authorized Payton's public key.
\item EC2 Instance Connect failed because the team's AWS role lacks
\texttt{ec2-instance-connect:SendSSHPublicKey}; the temporary security-group
rule added for it was removed immediately.
\item Caddy could not use port 80 while Color Buttons published it; the app was
moved behind Caddy on the private network.
\item The server's \texttt{compose.yaml} differed from the repository copy by a
stray Windows line ending; the file was rewritten with LF endings, and the
checksums now match.
\end{itemize}
\paragraph{Technical references.} \paragraph{Technical references.}
\href{https://docs.gitea.com/installation/install-with-docker/}{Gitea Docker installation}; \href{https://docs.gitea.com/installation/install-with-docker/}{Gitea Docker installation};
\href{https://docs.gitea.com/administration/config-cheat-sheet/}{Gitea configuration}; \href{https://docs.gitea.com/administration/config-cheat-sheet/}{Gitea configuration};
\href{https://caddyserver.com/docs/running\#docker-compose}{Caddy with Docker Compose};
\href{https://caddyserver.com/docs/automatic-https}{Caddy automatic HTTPS};
\href{https://education.github.com/pack}{GitHub Student Developer Pack}; \href{https://education.github.com/pack}{GitHub Student Developer Pack};
\href{https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html}{AWS Elastic IP documentation}. \href{https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html}{AWS Elastic IP documentation}.
+1 -1
View File
@@ -36,7 +36,7 @@ only once it exists and is reachable.
\multicolumn{4}{l}{\small\itshape Host Inventory (continued)}\\ \multicolumn{4}{l}{\small\itshape Host Inventory (continued)}\\
\toprule \textbf{Host name} & \textbf{Role} & \textbf{Platform} & \textbf{Status}\\ \midrule \toprule \textbf{Host name} & \textbf{Role} & \textbf{Platform} & \textbf{Status}\\ \midrule
\endhead \bottomrule \endlastfoot \endhead \bottomrule \endlastfoot
\texttt{ssw590-group-web} & Group deployment target. Runs Color Buttons and the Gitea work hub in \ChapterRef{GiteaFoundation}; Elastic IP \texttt{100.49.80.94}; DNS \texttt{git.inviscid.dev} verified October 15 (HTTPS pending). & Ubuntu 26.04 LTS on Amazon EC2, \texttt{t3.micro} & In service\\ \texttt{ssw590-group-web} & Group deployment target. Runs Color Buttons and the Gitea work hub in \ChapterRef{GiteaFoundation}; Elastic IP \texttt{100.49.80.94}; DNS \texttt{git.inviscid.dev}; HTTPS through Caddy. & Ubuntu 26.04 LTS on Amazon EC2, \texttt{t3.micro} & In service\\
\texttt{ssw590-dev-01} & Primary Linux host for the course. Target for service configuration, deployment exercises, and command-line work. & Ubuntu Server LTS on Amazon EC2 & Planned\\ \texttt{ssw590-dev-01} & Primary Linux host for the course. Target for service configuration, deployment exercises, and command-line work. & Ubuntu Server LTS on Amazon EC2 & Planned\\
\end{tabularx} \end{tabularx}
+3 -3
View File
@@ -10,8 +10,8 @@ This document records the configuration of the development environment used for
\CourseNumber\ and the working practices followed to produce it. \CourseNumber\ and the working practices followed to produce it.
Each chapter covers one subject, and each assignment adds or revises chapters Each chapter covers one subject, and each assignment adds or revises chapters
as recorded in the document history. The latest revision for Assignment~\AssignmentNumber\ as recorded in the document history. The latest revision for Assignment~\AssignmentNumber\
adds \ChapterRef{GiteaFoundation}, recording the initial Gitea deployment adds \ChapterRef{GiteaFoundation}, recording the Gitea deployment, HTTPS, firewall,
and the pending checks required to complete the assignment. persistence testing, and team workflow.
Earlier coursework chapters are retained. Earlier coursework chapters are retained.
\section{Scope and Constraints} \section{Scope and Constraints}
@@ -63,7 +63,7 @@ the Template Attribution page. The template's instructional example chapter and
its template-maintenance appendices have been removed, since they describe how its template-maintenance appendices have been removed, since they describe how
to author the template rather than the environment this document records. to author the template rather than the environment this document records.
\ChapterRef{GiteaFoundation} records the persistent work hub, team access, and pending domain, HTTPS, and workflow evidence. \ChapterRef{GiteaFoundation} records the persistent work hub, domain and DNS, HTTPS, firewall, persistence testing, and team workflow.
\section{Tools and AI Assistance} \section{Tools and AI Assistance}
\index{AI assistance} \index{AI assistance}
+1 -1
View File
@@ -44,7 +44,7 @@ only where a generated secret cannot be retrieved.
\endhead \bottomrule \endlastfoot \endhead \bottomrule \endlastfoot
Victor & Name.com registrar & Private account & Domain inviscid.dev; credentials held outside Git/report. Victor manages renewal; auto-renewal enabled.\\ Victor & Name.com registrar & Private account & Domain inviscid.dev; credentials held outside Git/report. Victor manages renewal; auto-renewal enabled.\\
\texttt{victor} & Gitea (Inviscid) & Unique & Site administrator; password kept outside this report and repository.\\ \texttt{victor} & Gitea (Inviscid) & Unique & Site administrator; password kept outside this report and repository.\\
\texttt{payton} & Gitea (Inviscid) & Temporary & Initial password delivered separately; mandatory change configured. Successful login reported; password-change completion not separately evidenced.\\ \texttt{payton} & Gitea (Inviscid) & Unique & Temporary password changed by Payton after first login; new password kept outside this report and repository.\\
Both authors & EC2 \texttt{ubuntu} & SSH key & Separate private keys remain on each author's workstation; only public keys authorized on EC2. No private keys in Git.\\ Both authors & EC2 \texttt{ubuntu} & SSH key & Separate private keys remain on each author's workstation; only public keys authorized on EC2. No private keys in Git.\\
\texttt{root} & \texttt{ssw590-dev-01} & None & No password. Administrative access is obtained through \texttt{sudo} by a named account, so that privileged actions are attributable.\\ \texttt{root} & \texttt{ssw590-dev-01} & None & No password. Administrative access is obtained through \texttt{sudo} by a named account, so that privileged actions are attributable.\\
\texttt{payton} & \texttt{ssw590-dev-01}, \texttt{ssw590-build-01} & Derived & Used only to authenticate \texttt{sudo}; \gls{ssh} password authentication is disabled, so this secret never crosses the network. \texttt{payton} & \texttt{ssw590-dev-01}, \texttt{ssw590-build-01} & Derived & Used only to authenticate \texttt{sudo}; \gls{ssh} password authentication is disabled, so this secret never crosses the network.
+3 -1
View File
@@ -23,6 +23,8 @@
\usepackage{minted} \usepackage{minted}
\setminted{fontsize=\footnotesize,breaklines,frame=single} \setminted{fontsize=\footnotesize,breaklines,frame=single}
\usepackage{tcolorbox} \usepackage{tcolorbox}
\usepackage{tikz}
\usetikzlibrary{positioning,arrows.meta,fit,backgrounds}
\usepackage{makeidx} \usepackage{makeidx}
\usepackage[colorlinks=true,citecolor=blue,linkcolor=blue,urlcolor=blue]{hyperref} \usepackage[colorlinks=true,citecolor=blue,linkcolor=blue,urlcolor=blue]{hyperref}
\usepackage[acronym,toc]{glossaries} \usepackage[acronym,toc]{glossaries}
@@ -40,7 +42,7 @@
% Replace these values for each assignment. % Replace these values for each assignment.
\newcommand{\AssignmentTitle}{Startup Foundation with Gitea} \newcommand{\AssignmentTitle}{Startup Foundation with Gitea}
\newcommand{\AssignmentNumber}{4} \newcommand{\AssignmentNumber}{4}
\newcommand{\DocumentVersion}{4.1} \newcommand{\DocumentVersion}{4.2}
\newcommand{\DocumentStatus}{Submitted Document} \newcommand{\DocumentStatus}{Submitted Document}
\newcommand{\CourseNumber}{SSW 590} \newcommand{\CourseNumber}{SSW 590}
\newcommand{\CourseName}{DevOps} \newcommand{\CourseName}{DevOps}
@@ -0,0 +1,60 @@
\begin{tikzpicture}[
font=\footnotesize,
>={Stealth[length=2mm]},
box/.style={draw, rounded corners=2pt, align=center, fill=white, inner sep=3pt},
ctr/.style={box, fill=devopsLightBlue, text width=34mm},
vol/.style={box, fill=devopsGray!12, text width=36mm},
ext/.style={box, text width=30mm},
pub/.style={->, thick, devopsBlue},
priv/.style={->, thick, devopsGreen},
adm/.style={->, thick, devopsGray, dashed},
blk/.style={->, thick, devopsRed, densely dotted},
lbl/.style={font=\scriptsize, fill=white, inner sep=1pt}
]
% External clients
\node[ext] (browser) at (0, 2.55) {Browser / Git client (any source)};
\node[ext] (ip) at (0, 1.5) {Instructor browser\\\texttt{\scriptsize http://100.49.80.94}};
\node[ext] (admin) at (0,-0.8) {Team admin (approved /32 sources)};
\node[ext] (scan) at (0,-2.5) {Any other source};
% EC2 contents
\node[vol] (cdata) at (6.6, 4.8) {\textbf{Volume} \texttt{\scriptsize inviscid\_caddy\_data}\\certificates, ACME account};
\node[ctr] (caddy) at (6.6, 2.15) {\textbf{Caddy} \texttt{\scriptsize caddy:2.10.2}\\TLS termination;\\only 80/443 publisher};
\node[ctr] (gitea) at (11.4, 3.0) {\textbf{Gitea} \texttt{\scriptsize 28.1.0}\\\texttt{\scriptsize gitea:3000}};
\node[ctr, text width=40mm] (buttons) at (11.4, 1.3) {\textbf{Color Buttons}\\\texttt{\scriptsize ssw590-group-buttons:3000}};
\node[vol] (gdata) at (16.0, 3.0) {\textbf{Volume} \texttt{\scriptsize inviscid\_gitea\_data}\\repositories, config,\\SQLite \texttt{\scriptsize gitea.db}};
\node[box, text width=34mm] (sshd) at (6.6,-0.8) {\textbf{sshd} (EC2 host), TCP 22};
\begin{scope}[on background layer]
\node[draw=devopsGreen, dashed, rounded corners, fill=devopsGreen!5, inner sep=3mm,
fit=(caddy)(gitea)(buttons),
label={[font=\scriptsize, text=devopsGreen, anchor=north west, yshift=-1pt]south west:Docker network \texttt{inviscid\_services} (private)}] (net) {};
\node[draw=devopsBlue, thick, rounded corners, inner sep=4mm,
fit=(net)(sshd)(gdata)(cdata),
label={[font=\scriptsize\bfseries, text=devopsBlue]above:EC2 \texttt{ssw590-group-web} (\texttt{us-east-1}) --- Elastic IP 100.49.80.94, no IPv6}] (ec2) {};
\end{scope}
% Security group wall
\coordinate (sgTop) at (3.25, 5.9);
\coordinate (sgBot) at (3.25,-3.0);
\draw[line width=2pt, devopsRed] (sgTop) -- (sgBot);
\node[font=\scriptsize, text=devopsRed, align=center, anchor=north, fill=white, inner sep=1pt] at ([yshift=-1mm]sgTop) {Security group\\\texttt{ssw590-web-sg}};
% Public flows (allowed by the security group)
\draw[pub] (browser.east) -- node[lbl, above, pos=0.72]{443; 80$\to$308} (caddy.west |- browser.east);
\draw[pub] (ip.east) -- node[lbl, above, pos=0.72]{80 HTTP} (caddy.west |- ip.east);
% Private flows
\draw[priv] (caddy.east |- gitea.west) -- node[lbl, above]{\texttt{\scriptsize git.inviscid.dev}} (gitea.west);
\draw[priv] (caddy.east |- buttons.west) -- node[lbl, below, pos=0.4]{by IP} (buttons.west);
\draw[priv] (caddy.north) -- (cdata.south);
\draw[priv] ([yshift=2mm]gitea.east) -- ([yshift=2mm]gdata.west |- gitea.east);
% Administrative path
\draw[adm] (admin.east) -- node[lbl, above, pos=0.72]{22 SSH} (sshd.west);
\draw[adm] (sshd.east) -- node[lbl, below]{SSH tunnel to \texttt{\scriptsize 127.0.0.1:3000}} (13.75,-0.8) |- ([yshift=-3mm]gitea.east);
% Blocked
\draw[blk] (scan.east) -- node[lbl, above, pos=0.62]{22, 3000, 222, 2019} ([xshift=-1.2mm]sgBot |- scan.east);
\node[text=devopsRed, font=\large\bfseries] at (sgBot |- scan.east) {$\times$};
\end{tikzpicture}
Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+4 -3
View File
@@ -14,6 +14,7 @@ Assignment 4 records the persistent Gitea service on the existing EC2 host,
Elastic IP association, and separate team access. It also records Student Pack Elastic IP association, and separate team access. It also records Student Pack
domain redemption, inviscid.dev registration and independently verified DNS, domain redemption, inviscid.dev registration and independently verified DNS,
organization ownership, repository import, and the first open issue-linked PR. organization ownership, repository import, and the first open issue-linked PR.
Public HTTPS, renewal and external exposure tests, container-replacement It also records Caddy-terminated HTTPS with a Let's Encrypt certificate and
persistence verification, and both completed reviewed-merge workflows remain renewal test, the EC2 security group with external exposure tests, a
pending in this draft. container-replacement persistence test, the product backlog, and two reviewed,
issue-linked pull requests.
+1
View File
@@ -11,6 +11,7 @@ link each entry to the changed chapter, table, figure, or controlled artifact.
\multicolumn{4}{l}{\small\itshape Document Update History (continued)}\\ \multicolumn{4}{l}{\small\itshape Document Update History (continued)}\\
\toprule \textbf{Date / Version} & \textbf{Author Initials} & \textbf{Change} & \textbf{Changed At}\\ \midrule \toprule \textbf{Date / Version} & \textbf{Author Initials} & \textbf{Change} & \textbf{Changed At}\\ \midrule
\endhead \bottomrule \endlastfoot \endhead \bottomrule \endlastfoot
October 15, 2026 / 4.2 & PCB & Added Caddy HTTPS with certificate, redirect, and forced-reissue renewal evidence; certificate-option comparison; security-group, network ACL, port-mapping, and external exposure tests; the container-replacement persistence test; architecture diagram and operations notes; issues 3--6 and reviewed PR 7. Updated the Hosts entry (HTTPS verified), Payton's Passwords hint, abstract, introduction, Cloud Docker note, glossary, and AI log. & \ChapterRef{GiteaFoundation}, \ChapterRef{Hosts}, \ChapterRef{Passwords}, \ChapterRef{CloudDockerDeployment}, \ChapterRef{AIPromptLog}\\
October 15, 2026 / 4.1 & VDRF & Recorded Student Pack redemption, inviscid.dev registration/renewal, DNS record and two independent lookups, organization ownership, repository import, issue 1, and open PR 2. Updated Hosts domain entry, Passwords registrar/account hints, abstract, AI log, and deployment handoff notes; HTTPS and reviewed merges remain pending. & \ChapterRef{GiteaFoundation}, \ChapterRef{Hosts}, \ChapterRef{Passwords}, \ChapterRef{AIPromptLog}\\ October 15, 2026 / 4.1 & VDRF & Recorded Student Pack redemption, inviscid.dev registration/renewal, DNS record and two independent lookups, organization ownership, repository import, issue 1, and open PR 2. Updated Hosts domain entry, Passwords registrar/account hints, abstract, AI log, and deployment handoff notes; HTTPS and reviewed merges remain pending. & \ChapterRef{GiteaFoundation}, \ChapterRef{Hosts}, \ChapterRef{Passwords}, \ChapterRef{AIPromptLog}\\
October 15, 2026 / 4.0 & VDRF & Added the initial Gitea foundation chapter with deployment, SQLite, Elastic IP, access evidence, and explicit pending DNS/HTTPS/workflow items. Updated Hosts inventory and cleanup, Passwords account/hint table and policy-versus-observation wording, metadata, abstract, introduction, and AI log. & \ChapterRef{GiteaFoundation}, \ChapterRef{Hosts}, \ChapterRef{Passwords}, \ChapterRef{AIPromptLog}\\ October 15, 2026 / 4.0 & VDRF & Added the initial Gitea foundation chapter with deployment, SQLite, Elastic IP, access evidence, and explicit pending DNS/HTTPS/workflow items. Updated Hosts inventory and cleanup, Passwords account/hint table and policy-versus-observation wording, metadata, abstract, introduction, and AI log. & \ChapterRef{GiteaFoundation}, \ChapterRef{Hosts}, \ChapterRef{Passwords}, \ChapterRef{AIPromptLog}\\
October 6, 2026 / 3.2 & VDRF & Replaced the preliminary tool proposal with Inviscid, its sample customer task, tentative stack, research question, and independent SWOT assessments of Prometheus and VictoriaMetrics. Updated the abstract and AI log. & \ChapterRef{ProjectProposal}, \ChapterRef{AIPromptLog}\\ October 6, 2026 / 3.2 & VDRF & Replaced the preliminary tool proposal with Inviscid, its sample customer task, tentative stack, research question, and independent SWOT assessments of Prometheus and VictoriaMetrics. Updated the abstract and AI log. & \ChapterRef{ProjectProposal}, \ChapterRef{AIPromptLog}\\