>> Hey, friends.
You know running my application in a container means
it's completely secure. Well, not quite.
In this episode, Jessica Deen shows me the best
practices for running a GitHub Action CI/CD
workflow to run a secure serverless container
with Azure Container Instances.
Today on Azure Friday.
[MUSIC]
>> Hey, friends. I'm Scott Hanselman,
and it's another episode of Azure Friday.
I'm here with Jessica Deen, and she is going to show me
all awesome stuff with CI/CD. How are you?
>> I'm doing excellent. How are you, Scott?
>> I am feeling pretty good
until I saw that your office's cool than mine.
Now I feel we're going to
an epic office battle that we're going to have to.
>> We are, but I don't have a silver YouTube thing behind me.
>> I appreciate that. Azure Friday,
but I think has a couple of those as well.
Yeah, we're doing okay here.
Your office is amazing.
>> Thank you.
>> I'm sure that your demo was amazing as well.
We're going to be talking about ACI today.
I usually just pick a container.
I write a Docker file and I don't give it a lot of thought,
it sounds I maybe should.
>> You definitely should,
just because something is running in a container does not mean
that you automatically have security baked in,
unless you've been very intentional about the way that you've
designed that container and built it with security in mind.
>> That means that the container instances that I'm
running on local host may not be appropriate.
It just out of the box for
the ones I'm going to put up in production?
>> Correct. Not only do you have to consider
the way that you're structuring your container itself,
but also your application code.
You want to make sure that you aren't using
any vulnerable packages in
your application or any vulnerable dependencies.
Then on top of that, when you build your container,
one of the biggest mistakes and perhaps the one that
a lot of people overlook is they
ultimately run their container as route.
When you run your container is route,
you open up a potential security exploit.
Where if malicious code we're running inside that container,
it could actually jump out and take
control of the underlying host.
>> I think you're right that
there are people who think a certain way,
and I think I may be one of those people that
when we understood container technology,
we figured that we had our little space here.
We have our little hotel room and we can pretty much thrash
the hotel room and then leave and it will be
put back together the way we want it.
But container technology isn't a guarantee for security.
>> It's not a one size fits all.
I'm just going to throw it up into
this little bundle of joy and then throw it over into the Cloud.
Now it's magic. There are
some key things you can do and they're actually pretty easy.
I can share my screen if you'd like to see.
>> Let's do it.
>> We have a node based application.
It's an Image Resizer API that we're going to
use to have some fun and resize some images.
It's actually using a node package called sharp.
We'll talk a little bit more about
the application after we show you the Docker file.
If we take a look at the Docker file,
you'll notice that right off the bar from the first line,
which is where I set my base image,
I'm using node, for a node application.
But you'll also notice that I have alpine.
Now this is very intentional because a lot of times people
will just start a Docker file like this from node.
But the problem with that is,
that's using a Debian-based image.
Debian-based images, well,
that might be something that you're interested in,
that might be something that you actually need.
I usually challenge people to
consider if they need Debian or if they need Alpine.
The reason for that is
Debian-based images are significantly larger than Alpine.
When building containers, one of
the easiest best practices to really be intentional with,
is to build as small of a container as you possibly can.
That usually means starting with something like alpine and node.
Those are the only two things that I
need is a Linux kernel and Node.
The next line starts setting me up for running this in Azure.
I'm adding a package called
libcap that's going to make sure that I
can set permissions to have access to privilege ports.
We'll circle back to this in a second.
But one of the first things I want to highlight
is the fact that when I'm adding this package,
I'm using no cache.
This is another best practice from CIS,
or Center for Internet Security,
to make sure that you're not caching
the packages that you're downloading in your container.
The next several lines are pretty standard.
They're lines that you've probably written if
you've written a Docker file before.
I'm setting a work directory for my application will live.
I'm copying my package.json files.
I'm running npm install,
and then I'm bundling up and copying my app source code.
All of that is pretty standard.
But on lines 22 and lines 25
and 26 is where I'm starting to
make sure that the container is secure.
First off, I'm making sure that the container
is not running as route.
If you don't explicitly tell
the container that you want to set a user level,
your container is automatically going to run as route permissions,
which is going to open up a vulnerability,
especially if you plan to run this in production.
Now because I'm setting a specific application user,
I'm creating an application group.
I am assigning it to that group and then I'm
owning my application folders.
You'll notice that I have,
on line 22, setcap.
That's why I'm using that package that I installed to
bind my user and my service to node.
This is going to allow the application to run on port 80,
which is considered to be a privileged port.
Everything is very structured, very intentional.
I'm considering where I'm going to put this.
I'm making sure just the application and
just the language that I need has access to the port that I need.
Then from there, I'm exposing my port and I'm running npm start.
It's still very simple,
but I'm using only a few lines to make sure that I secure that.
>> You're really trying to do,
print the least privilege, right?
>> The least privileged that I need to
make sure that my application is successful.
The way that I'm adding the user in
this instance would be
different if you were using something that's Debian.
You also have to be aware of Alpine verse Debian,
Apk for adding packages,
add groups, add users.
But as long as you're thinking about the functionality
and the future plan of where you plan to use this, it's very easy.
You can add it with just a few lines.
Once you build this,
you're going to want to make sure that you
put it somewhere secure.
I'm not just going to take this now
secure image and throw it up on a public repository.
I want it to be somewhere that I control.
I'm going to use Azure Container Registry to
handle the storage of that security image.
Then now I guess I can show you.
You want to actually see the application?
>> Cool, because we want to see it happen.
>> We do. First, before we see it happening,
because I have a fun thing that we're going to do
to make a change and watch this happen live.
Let's take a look at our application.
All the code and everything lives here.
We have our Image Resizer folder,
having up some scripts that do some cool magic for you.
But here's our ultimate Image Resizer.
We'll just refresh this here.
You can see there isn't anything crazy going on, this is all live.
I can do some cool things if I wanted
to have an actual interface here.
I can change my height,
I can change my width.
I can even change the output image format
if I wanted to optimize it for the web.
Or I can also attach query string at the end.
For anyone who's watching in the open source community,
sharp is a very popular image processing application,
which is what this Image Resizer uses.
You can even use it to transform your images,
rotate your images, crop your images.
There's a bunch of different query parameters
you can attach the end.
But we're going keep it simple with this interface for the demo.
Scott, I know you really like chipotle.
>> I have been known to like a taco or two.
>> I thought it might be fun if we make you your very
own chipotle Image Resizer,
so you could resize your images for use in TikTok videos.
If you want to share with the world how much you love chipotle.
Let's check this out. I'm going to go on over
to my little web browser here.
I have some chipotle images that we're going to find to optimize
this Image Resizer for your chipotle Image Resizer.
I don't know if you have a preference
as far as which picture you like?
>> Why don't we pick the second one there,
the one on the Wikimedia Commons.
>> This one right here?
>> Yeah.
>> I'm going to go ahead and copy the image address.
Now before we use our Image Resizer,
I'm going to go back over to the code and we're going to start
customizing your very own Image Resizer here.
I'm going to go ahead and name this Scott's Image Resizer.
I'm going to go and add an image source equals.
We're going to paste that link in.
I'm just going to go ahead and close that.
Now I'll save and we're going to test this out locally first,
before we go and push this over into our Azure Container Instance.
I'll flip back over here and my local is serving on port 80.
Now that's quite big. Right now
that drops everything below the fold.
Let's actually use our live and production
basic Image Resizer to resize this image.
I'll go over here and paste that image link in.
I go ahead and resize it.
I could save this image,
but instead, I'm going to copy that link address.
We'll go back over here.
I'm just going to paste this new link in.
Go ahead and save that.
We'll restart our node application here,
go back over to our local just to test.
Now that looks much better.
I want to get your very own Scott's Image Resizer
with this picture over into Azure.
The first thing we're going to do is clear this.
I'm going to make sure I'm working on a PR branch here,
which for some reason it says that I'm not in a repo.
I'm going to do git brach and see.
I've tested this a few times, our git checkout-b.
We're going to name this scott.
Clear? I'm going to go ahead and do git add and
see what it suggests. There we go.
We're going to add our new index.
We're going to do git commit.
I'm going to call this scott, git push.
>> There we go. I have to make sure I set my upstream.
All of this good get best practices.
All right. So that just pushed over to Scott branch.
So we're going to go over here.
You can see that Scott had pushes less than a minute ago.
We're going to do a compare pull request.
We're going to pretend that we've also done
our best practices and
our due diligence in writing awesome notes here.
I'm just going to click "Create pull request."
Now, you'll notice as soon as I've created this pull request here,
GitHub actions is going to start running some checks,
and this is going to run a PR workflow.
Now, this PR workflow is going to do a few things.
First, it's going to take a look at our Dockerfile,
and it's going to start building our Docker container
in the order that we defined it in that Dockerfile.
So it's going to make sure that it uses,
know that it's setting those permissions,
it's adding those packages.
Then it's actually before it
pushes over to Azure Container Registry,
it's going to scan our image re-sizer container.
So when it gets to that step,
you'll notice that it's going to scan
that container for vulnerabilities.
That's for anything that the Node application is using,
any dependencies in there,
it's using Restify to serve as that web server.
Make sure that Restify doesn't have any vulnerabilities,
makes sure that Sharp doesn't have any vulnerabilities.
Then it's also going to use
an open-source tool called Docle to check for,
again, those CIS or Center for Internet security best practices.
It's going to scan for things
like making sure I'm not running as root.
There's actually 12 standard best practices.
I have a little guide right here just to show you.
So you want to make sure that you create a user for the container.
In a normal production scenario,
you do want to make sure that you use
trusted base images for containers,
and you can use something like Azure policy to define that.
You want to make sure that you potentially add
health checks to your container image that you remove,
Set UID that you use copy instead of add,
that you're not storing any secrets.
There's a whole bunch of practices and using
things like Docle and even Trivy which
this Azure Container scan action
does will help prevent you from pushing
potentially compromised images into
your Azure Container Registry that
ultimately you'll end up using in
production or in Azure container instances.
If we switch back over here,
you'll notice this now finished our build,
and if I scroll up while it's
deploying to Azure Container Instances,
and we take a look at our container scan.
We'll get a confirmation that no vulnerable images were
detected and no CIS benchmark violations were detected.
So we get full end-to-end scanning and we get
additional confidence of the security of our image.
Go back over and see just how well this is deploying.
All right, it is not finished,
it has deployed over to container instances.
If I take a look at the logs here,
you'll notice that the app has been deployed and I actually even
have a suffix here that tells me the PR number that I opened.
PR 17, that matches PR 17.
But the cool thing is,
is because we're using GitHub Actions workflow,
we can define any order of tasks that we want.
For example, if we go over to our conversation,
I've also had GitHub Actions Bot,
add a comment to our poll request
where if Scott were to go to this repo,
he could click right here and see his very own image
re-sizer now live over in container instances.
>> We wanted to plot, sorry.
>> I thought you might. This is
your own private one that you can now use.
But if we go back over to our,
let's see, let's go back over to our actions run.
This is our most recent one that we ran and I want to show
you our workflow file.
So this is how simple it is.
Now, GitHub Actions is incredibly customizable.
I can use any amount of triggers.
In this instance, we wanted to have a trigger that
anytime I make a pull request to go into the main branch,
this actions fires off.
I set up some basic environment variables,
and then I start some simple jobs.
I check out my main branch.
I log into Azure credentials
and or login to Azure using Azure credentials.
You'll notice something that says Secrets.
I'm able to store secrets securely in
this workflow without passing anything in plain text.
The same secrets apply to my Container Registry.
So I don't have anything visible to the public eye.
Then I'm doing normal commands I would do locally,
where I'm building my image re-sizer API.
I'm using that scan functionality to scan my image,
and then I'm pushing that over to
my Azure Container Registry from there, check this out.
This is my favorite part in
the deployment step, we do the same things.
We check out our code again.
We make sure we're logged into Azure.
But this step right here is our container instance.
It's using a native supported GitHub actions aci-deploy.
We defined our resource group.
We give our container instance a name.
We give our DNS naval a name,
that's also FQDN or Fully Qualified Domain Name.
We set our image,
we make sure we log in,
and right down here is where the magic
happens to back Azure storage to it.
It mounts it directly into
the container to whatever the local volume point is.
So inside my container,
I have all images saved locally to a local images folder,
and I just mount Azure Storage to that path.
>> Cool. I actually learned a lot there.
There's a lot to absorb.
Where can I learn more about these things?
There are some resources that you had there
that it sounds like I should check out.
>> There are you can always check out
docs@Microsoft.com to check out about Azure Container Instances.
But if you're curious specifically about
container best practices and using tools like Docle and Trivy,
which the GitHub Actions container scan tool uses.
Tomoya actually has a great article that I
referenced on how to keep your Docker images secure.
In the article, we go over
CIS benchmarks and again using tools like Trivy and Docle.
>> Fantastic. So we use VS Code, we used Docker,
we used GitHub actions were used ACI,
and now we have a nice secure workflow,
and now that workflow is clear,
that that outer loop is handled.
It's going to just stay secure and
keep watching for anything that could potentially go wrong.
>> Absolutely. Because everything's in a nice automated workflow.
So anytime you make a change to
your production application or now
your very own Scott's image resizer,
the workflows will automatically fire off.
There's one more point to add when you are
using things like Azure Container Instances,
which is serverless, it's very easy to spin up,
but it's also very easy to tear down.
One of the areas that's challenging for people
is they'll shut down their container instance,
they'll shut down their serverless resources,
and forget that they're also using other resources,
like for example, Azure storage.
So those also need to have
some cleanup jobs that are running periodically that may be clear
out your images directory or clears out if you're
using databases or whatever other back end storage.
You can use GitHub actions to
also run on a scheduled timer where it
will check for any kind of stale PRs
or stale scenarios for serverless,
and it'll run cleanup jobs or clean-up
batch jobs to clear out anything
in those backed resources after
your serverless infrastructure has been spun down.
>> Very cool. Well, I
appreciate you spending some time with me today.
>> I appreciate you hanging out with me as well.
>> All right, I am learning all about Azure Container Instances,
best practices for security,
and other things with GitHub Actions today on Azure Friday.
>> Hey, thanks for watching this episode of Azure Friday.
Now I need you to like it.
Comment on it, tell your friends,
retweet it, watch more. Azure Friday.
[MUSIC]