Azure DevOps Git repository options

Azure DevOps is a big product and often users start using it without fully explore all the possibilities. As an example, when it is time to work with Git Repositories, users just create repositories and start working without any further configuration.

If you navigate to the Repos section of Project Settings page, you can configure lots of options for repositories.

Security is probably the most important setting, because it determines who can access that specific repository and what permission each user / group has in the context of that very specific repository.

image

Figure 1: Security settings of a Git Repository

You can simply select repositories (1) then select the specific repository (2) then the user / group (3) and finally set permission level. Permissions can be also set for each different branch of the repository and you can also select the root of the repositories (Git repositories node) to set default security for all repositories.

Thanks to hierarchy you can set permission for all repositories of a Team Project then change permission to child repositories and even single branches

For each repository you have also some options (Figure 2) to allow forking and enable / disable automatic linking and resolution of work item included in comments. You can also select all the branches that will be indexed for code search.

image

Figure 2: Options for repository

Finally, in Figure 3 you can see policies for the entire repository. These policies are really important because allows you to have an healthy repository. As an example I strongly suggest you to check the Case Enforcement rule, that prohibit change casing of a file in a commit (a problem that can lead to headache). You can also limit maximum file size and limit path length, or blocking pushes that contains files with specific pattern name.

Additionally you have also a pattern for author email, to limit commit authors to a specific set of allowed emails. All policies in Figure 3 can be set on single repository or to the root node to enable them for all repository.

image

Figure 3: Policy page for a repository

If you check policies for a specific branch, instead of the settings in Figure 3 (valid for the entire repository), you will be presented with the configuration in Figure 4 that allows you to specify pull request rules.

image

Figure 4: Pull request rules.

These policies can prevent pushing directly on specified branch, enforcing a process of pull request to reintegrate a branch on specified branch. You can use one or more build to automatically verify quality of merged code, automatically require formal approvation from external service or code reviewers.

Pull requests are the only way to have full control on a branch, having a full review for each increment of code that should be merged on that branch

In that configuration page, the one I liked the most is Limit merge type option, that allows me to limit the type of merge allowable to close the pull request. Since I’m a big fan of linear history, I strongly suggest you to leave only Rebase and fast forward, to enforce a strict linear history.

image

Figure 5: Limit merge type option

Finally you should be aware of the possibility to specify branch policies by convention, as shown in Figure 6. This options is pretty recent, you can choose Cross-Repo policies, then add branch protection and finally specify the name of the branch to protect, develop in my example.

SNAGHTML58a82a

Figure 6: Cross repo policies

Now you can specify policies for specific branches belonging to any repository in this Team Project, as an example you can enforce branch protection for all develop and master branches for every repository.

If you are using Git in Azure DevOps I strongly suggesting you to have a deep look to all the options to use the tool at the full power.

Gian Maria.

Do not trust user input part 2

After we fixed our code in part 1 of this serie, we continue to expand our API adding a method to select  a Customer. Northwind database Customer table has an id of type string, so we could start with this very bad, bad, bad piece of code.

image

Figure 1: Another bad example of API vulnerable with Sql Injection

Again the question is: what is the most critical error in that piece of code? If you answer “Query with string concatenation” probably you are wrong. Indeed that is a huge problem, but in my mind is accepting a string from the user is still the number one problem.

Remember that accepting a string from the user basically means that you accept really every possible sequence of characters, probably not what you really want.

If in part 1 example using a parameter string for an integer id is a real stupid error, someone could be tempted to affirm that, since the id of the customer is a string, it is normal for the API to accept a string. Believe me, the answer is: NO!

If I look at northwind database, id is composed by 5 uppercase letters, every id has this specific pattern and is not random. This is the reason why I can define a specific class to represent a valid customer id that throws exception if anything different from a valid pattern is passed as argument.

public class CustomerIdClass
{
    public CustomerIdClass(String customerId)
    {
        if (customerId.Length != 5)
            throw new ArgumentException("Invalid Id", nameof(customerId));

        if (customerId.Any(c => !Char.IsLetter(c)))
            throw new ArgumentException("Invalid Id", nameof(customerId));

        Id = customerId;
    }

    public String Id { get; private set; }
}

As you can see, this class is really simple, it represents the id of a customers that can be created only from a string composed exactly by 5 letters. Customer object has CustomerId property of type CustomerIdClass and this will ensure that you cannot create a customer with invalid id. This enforces Customer Id pattern in every instance in your code, preventing you to create a customer with invalid id like “3”.

This type of technique is extremely useful also for security purposes, because you can now change your API method to construct a valid CustomerId.

image

Figure 2: GetCustomer method now construct a valid CustomerIdClass before issuing the query

Thanks to this code, you can still ask for customer with a simple get:

image

Figure 3: Simple query for customers

Any request that deviates from the standard pattern of a customer Id (5 chars) returns an error

image

Figure 4: Error returned from wrong id

Now it is mandatory that you should also change the way you are issuing your query to Sql Engine removing the injection, but we can all agree that an attacker has an hard life to try injection of any kind using only 5 letters even if your query is really bad and directly concatenates strings.

The lesson is always the same: limit what the user can pass as parameter to the exact pattern of the data you can expect, instead of allowing any string to be passed.

Gian Maria.

Do not trust user input

It is time to start blogging a little bit about security, because injection is still high in OWASP TOP 10 and this implies that people still trust their users. Remember, you should not trust your users, never, never, never, because for 10.000 good users there could be 1 bad user, and he/she is enough to damage your organization.

Here you have a really bad, bad, bad, bad, piece of code that is meant to allow product retrieval from northwind database Products table.

image

Figure 1: Standard piece of code that suffer from SQL Injection.

I hear you screaming something like: if you use Nhbernate, Entity Framework or parameterized commands the problem will go away, never ever build sql string from raw user output. Legit, but my question is: What would be your first fix for code in Figure 1? If your answer regards how you create the query to the database, the answer is only partially correct.

The real problem of function in Figure 1 is that productId is an integer in the database, but the method admits a string as parameter, this basically allows anything to be passed as productId. This kind of stupid errors are simple to made and since everything works as expected often are never corrected.

Now my question is: how dangerous is the above code? Rest assured that such code leads to a full compromise of your database and potentially could compromise your entire system. Any script kiddie can use SqlMap to test if the url is vulnerable

image

Figure 2: A simple call and sqlmap find that the url is vulnerable

Voilà, the url is vulnerable, the attacker can do almost everything to your database trough your application, exfiltrate data, deleting and modifying data and if you have the bad habit of using server admin (like sa) in your connection string, every database is compromised. The attacker can also use the –privileges options to understand the privilege of code running the injection.

image

Figure 3: Here are all privilege that sqlmap can use, it seems that someone access the database with an administrator connection string, too bad.

The –current-user option list the current user, actually I’m running the .NET core application in a console with my Windows User, and indeed sqlmap is able to get the current user of the system.

image

Figure 4: Current user detection on the system

Believe me, if database engine is very old or it is bad configured, you can even transfer files to and from the system or open a shell and compromise the entire system (https://niiconsulting.com/checkmate/2014/01/from-sql-injection-to-0wnage-using-sqlmap/). You should trust me, you really do not want to find yourself in this situation.

A single entry point vulnerable to SQL Injection could compromise the entire system

Since the real flaw is accepting a string for product id, a much more secure version is obtained simply declaring the parameter as Int32.

image

Figure 5: A real secure version of the API

Even if code in Figure 5 still contains a query created with string concatenation (that should be changed immediately after changing parameter type because it is a bad error) productId parameters is now declared as integer and the attacker cannot use SQL Injection any more. This solution is better because it not only protects you from known sql injection tricks, but it limits user input to an integer, reducing any parameter manipulation technique he/she can use. Anything that is not an integer is simply not accepted.

image

Figure 6: This is what an attacker find when he/she tries to send something that is not an integer to the system.

You can now run again sqlmap against the api and you can verify that the endpoint is not vulnerable anymore.

Remember, limiting what your users can send to your code to the minimum value set that allows the code to work greatly limits the risk of  receiving a dangerous payload.

Gian Maria.

Windows Docker Container for Azure Devops Build agent

Thanks to Docker Compose, I can spin off an agent for Azure Devops in mere seconds (once you have all the images). Everything I need is just insert the address of my account a valid token and an agent is ready.

With .NET core everything is simple, because we have a nice build task that automatically install .NET Core SDK in the agent, the very same for node.js. This approach is really nice, because it does not require to preinstall too much stuff in your agent, everything is downloaded and installed on the fly when a build needs that specific tooling.

.NET core Node.Js and other SDK can be installed directly from build definition, without any requirement on the machine where the agent is running

In my projects I need also to build solution based on Full Framework SDK and clearly I want to use the very same Docker approach for Agents. Everything I need is a machine with Windows Server2019 with docker enabled and looking in the internet for needed Dockerfile or pre build images on public registry.

I’ve started with a nice article that explain how to install Build Tools inside a container. This articles gives you a dockerfile that basically is doing everything, just docker build and the container is ready to be used.

The only real modification I need is excluding most of the packages I’m not using in my project; I’ve also changed base image to use .NET Framework 4.8 and not 4.7.2.

image

Figure 1: RUN instruction to install build tools inside a Docker Image.

Be prepared to wait a litlle bit for the image to be built, because it downloads all the packages from network and then install into the machine. To build the container image from dockerfile you can simply type.

docker build -t buildtools2019:1.0 -m 4GB .

Now I need to modify my Azure Devops Agent dockerfile built following MSDN instructions  and change base image to FROM buildtools2019:1.0 this will base the agent image on the new image built with all .NET sdk for full framework and all build tools. With this image the agent can build Full Framework projects.

Creating a Docker Images with all needed tools for full framework SDK was really easy.

The other challenge on creating your build agent in Docker is having all the requirements in other docker images, for SQL Server I’ve rebuild the container on top of Windows Server Core 2019 image and it runs just fine, for MongoDb I’ve enabled experimental feature to mix Linux and Windows container so I’ve used a Linux based mongodb image with no problem.

My only remaining dependency was ElasticSearch, because I’m using an old version, 2.4 and I’d like to have that image running in Windows Container. It is not difficult to find some nice guy that published dockerfile for Elasticsearch, here https://github.com/StefanScherer/dockerfiles-windows you can find lots of interesting images for Windows. In my situation I only need Elasticsearch, so I’ve gotten the image, modified to install the version I need and the game is done.

Now I need to have some specific plugins installed in Elasticsearch for my integration tests to run; these situations are the ones where Docker shows its full powers. Since I’ve already an image with elasticsearch, I only need to create a DockerFile based on that image that install required plugin.

FROM elasticsearch_custom:2.4.6.1

RUN "C:\elasticsearch\bin\plugin.bat" install delete-by-query

Wow, just two lines of code launch docker build command and I have my ElasticSearch machine ready to be used.

image

Figure 2: All my images ready to be used in a Docker compose file

Now everything you need to do is create the docker compose file, here is an example

version: '2.4'

services:
  agent:
    image: azdoagent:1.0
    environment:
      - AZP_TOKEN=****q
      - AZP_POOL=docker
      - AZP_AGENT_NAME=TestAlkampfer
      - AZP_URL=https://dev.azure.com/prxm
      - NSTORE_MONGODB=mongodb://mongo/nstoretest
      - NSTORE_MSSQL=Server=mssql;user id=sa;password=sqlPw3$secure
      - TEST_MONGODB=mongodb://mongo/
      - TEST_ES=http://es:9200

  mssql:
    image: mssqlon2019:latest
    environment:
      - sa_password=sqlPw3$secure
      - ACCEPT_EULA=Y
    ports:
      - "1433:1433"

  mongo:
    platform: linux
    image: mongo
    ports:
      - "27017:27017"

  es:
    image: elasticsearch_jarvis:1.0
    ports: 
      - "9200:9200"

Once the file is ready just start an agent with

docker-compose -f docker-compose.jarvis.yml  up –d

and your agent is up and running.

image

Figure 3: All docker images up and running

Then I go to my C# project and included all the docker file needed to create images as well as docker compose file directly in source repository. This allows everyone to locally build docker image and spin out an agent with all the dependency needed to build the project.

If your organization uses azure or any other container registry (you can also push to a public registry because all these images does not contains sensitive data) spinning the agent is only a matter of starting docker composer instance.

After a couple of minutes (the time needed by azure devops agent to download everything and configure itself) my builds start running.

image

Figure 4: A build running a Full Framework solution with integration tests on MongoDB and Elasticsearch is running in my docker container.

From now on, every time I need to have another agent to build my project, I can simply spin out another docker-compose instance and in less than one minute I have another agent up and running.

Gian Maria.

Why I love DevOps and hate DevSecOps

DevOps is becoming a buzzword, it makes hype and everyone want to be part of it, even if he/she does not know exactly what DevOps is. One of the symptoms of this is the “DevOpsEngineer”, a title that does not fit in my head.

We could debate for days or years on the right definition of DevOps, but essentially is a cultural approach on building software focused on building the right thing with the maximum quality and satisfaction for the customer. 

Remember, DevOps is a cultural approach based on transparency and inclusion, not a set of tools/practices

In my head DevOps is nothing really different from Agile, it has just a different perspective. And I know, probably most of you are crying out loud because we had lots of guru, articles, sites telling you the difference from Agile and DevOps, but I simply do not care. If you think that DevOps is about continuous deployment and only tools and practices, you are probably wrong. I can admit that tools and practices can be a backbone of DevOps culture, everything should start with culture, collaboration, inclusion and transparency, tools and practices come later in the game.

What I care, as a professional that gets paid to create software, is the satisfaction of the Customer, because it brings me more work and makes me proud of my work, after all, in this industry, we all love our works. I read “The Goal” lots of years ago, and it is still so actual, the Theory of Constraint is still actual, even in software. In my mind DevOps is just another tentative of changing the approach of making software for the good of the team, ops, Customer and users.

Given that, what is a DevOps Engineer? Giving DevOps prefixed roles in a DevOps culture is really bad, because every person of the team is part of DevOps: Customer + Developers + Operationals.

DevOps culture permeates work environment and we do not need DevOps XXX roles, everyone is part of DevOps culture and if you urge the need to find a DevOps Engineer it just means that other members in the team are out of DevOps culture. A DevOps XXX is just a patch in your culture problem and it will not just work.

Given that, since I love DevOps I hate every DevXXXOps, because there is nothing more to add to DevOps.

This is why I hate with all myself DevSecOps; since DevOps is now a buzzword as Security, why not to create a super buzzword like DevSecOps?

Let me be crystal clear, if you claim that your organization has a DevOps culture and you do not care about security, you are doing it dead wrong. Security is paramount, it should be part of every professional / practice / culture and it should permeate every part of software lifecycle. If you think that you need to add Sec to DevOps it just means that you are not caring about security in your culture, and this is a HUGE problem that should be address before bringing new Buzzword into the game.

Gian Maria.