diff --git a/.github/workflows/int.yml b/.github/workflows/int.yml index 26b75ef..cbcf9cb 100644 --- a/.github/workflows/int.yml +++ b/.github/workflows/int.yml @@ -12,37 +12,27 @@ jobs: integration-test: permissions: contents: read - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: step-security/harden-runner@v2 - with: - allowed-endpoints: - api.github.com:443 - github.com:443 - golang.org:443 - int.api.stepsecurity.io:443 - objects.githubusercontent.com:443 - pipelines.actions.githubusercontent.com:443 - proxy.golang.org:443 - step-security-agent.s3.us-west-2.amazonaws.com:443 - storage.googleapis.com:443 - sts.us-west-2.amazonaws.com:443 - - name: Checkout - uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 - - name: Set up Go - uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 - with: - go-version: 1.19 - - run: sudo go test -v - - run: go build -ldflags="-s -w" -o ./agent - - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@ea7b857d8a33dc2fb4ef5a724500044281b49a5e - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-west-2 - - run: aws s3 cp ./agent s3://step-security-agent/refs/heads/int/agent --acl public-read - - name: Integration test - uses: docker://ghcr.io/step-security/integration-test/int:latest - env: - PAT: ${{ secrets.PAT }} + - uses: step-security/harden-runner@v2 + with: + egress-policy: audit + - name: Checkout + uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 + - name: Set up Go + uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 + with: + go-version: 1.19 + - run: sudo go test -v + - run: go build -ldflags="-s -w" -o ./agent + - name: Configure aws credentials + uses: aws-actions/configure-aws-credentials@ea7b857d8a33dc2fb4ef5a724500044281b49a5e + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + - run: aws s3 cp ./agent s3://step-security-agent/refs/heads/int/agent --acl public-read + - name: Integration test + uses: docker://ghcr.io/step-security/integration-test/int:latest + env: + PAT: ${{ secrets.PAT }} diff --git a/agent_test.go b/agent_test.go index ffc0b66..f23895d 100644 --- a/agent_test.go +++ b/agent_test.go @@ -135,7 +135,25 @@ func TestRun(t *testing.T) { }, t.Log)) - httpmock.RegisterResponder("GET", "https://dns.google/resolve", // no query params to match all other requests + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=actions-results-receiver-production.githubapp.com.&type=A", + httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) + + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=artifactcache.actions.githubusercontent.com.&type=A", + httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) + + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=pipelines.actions.githubusercontent.com.&type=A", + httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) + + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=token.actions.githubusercontent.com.&type=A", + httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) + + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=codeload.github.com.&type=A", + httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) + + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=vstsmms.actions.githubusercontent.com.&type=A", + httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) + + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=vstoken.actions.githubusercontent.com.&type=A", httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) httpmock.RegisterResponder("GET", "https://apiurl/v1/github/owner/repo/actions/subscription", diff --git a/dnsproxy.go b/dnsproxy.go index d911ecd..87d5a38 100644 --- a/dnsproxy.go +++ b/dnsproxy.go @@ -116,12 +116,18 @@ func (proxy *DNSProxy) isAllowedDomain(domain string) bool { func (proxy *DNSProxy) ResolveDomain(domain string) (*Answer, error) { url := fmt.Sprintf("https://dns.google/resolve?name=%s&type=a", domain) - + fallbackUrl := fmt.Sprintf("https://cloudflare-dns.com/dns-query?name=%s&type=A", domain) retryCounter := 0 var httpError error var resp *http.Response for retryCounter < 2 { - resp, httpError = proxy.ApiClient.Client.Get(url) + requestUrl := url + if retryCounter == 1 { + requestUrl = fallbackUrl + } + req, _ := http.NewRequest("GET", requestUrl, nil) + req.Header.Add("accept", "application/dns-json") + resp, httpError = proxy.ApiClient.Client.Do(req) if httpError != nil { retryCounter++ } else { @@ -251,15 +257,19 @@ func (proxy *DNSProxy) processTypeA(q *dns.Question, requestMsg *dns.Msg) (*dns. queryMsg := new(dns.Msg) requestMsg.CopyTo(queryMsg) queryMsg.Question = []dns.Question{*q} + domains := map[string]string{ + "dns.google.": "8.8.8.8", + "cloudflare-dns.com.": "1.1.1.1", + } - if q.Name == "dns.google." { - rr, err := dns.NewRR("dns.google. IN A 8.8.8.8") + if ip, ok := domains[q.Name]; ok { + rr, err := dns.NewRR(fmt.Sprintf("%s IN A %s", q.Name, ip)) if err != nil { return nil, err } - proxy.Cache.Set(q.Name, &Answer{Name: q.Name, TTL: math.MaxInt32, Data: "8.8.8.8"}, false) + proxy.Cache.Set(q.Name, &Answer{Name: q.Name, TTL: math.MaxInt32, Data: ip}, false) return &rr, nil } diff --git a/dnsproxy_test.go b/dnsproxy_test.go index 9837cf8..1a4a3b5 100644 --- a/dnsproxy_test.go +++ b/dnsproxy_test.go @@ -24,10 +24,12 @@ func TestDNSProxy_getResponse(t *testing.T) { auditCache := InitCache(EgressPolicyAudit) blockCache := InitCache(EgressPolicyBlock) rrDnsGoogle, _ := dns.NewRR("dns.google. IN A 8.8.8.8") + rrDnsCloudflare, _ := dns.NewRR("cloudflare-dns.com. IN A 1.1.1.1") rrDnsTest, _ := dns.NewRR("test.com. IN A 67.225.146.248") rrDnsNotAllowed, _ := dns.NewRR(fmt.Sprintf("notallowed.com. IN A %s", StepSecuritySinkHoleIPAddress)) rrDnsAllowed, _ := dns.NewRR("allowed.com. IN A 67.225.146.248") rrDnsMcr, _ := dns.NewRR("westus.data.mcr.microsoft.com. IN A 67.225.146.248") + rrDnsFallback, _ := dns.NewRR("testfallback.com. IN A 67.225.146.248") allowedEndpoints := make(map[string][]Endpoint) allowedEndpoints["allowed.com."] = append(allowedEndpoints["allowed.com."], Endpoint{domainName: "allowed.com"}) allowedEndpointsTest := make(map[string][]Endpoint) @@ -51,6 +53,9 @@ func TestDNSProxy_getResponse(t *testing.T) { httpmock.RegisterResponder("GET", "https://dns.google/resolve?name=notfound.com.&type=a", httpmock.NewStringResponder(200, `{"Status":3,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"notfound.com.","type":1}],"Authority":[{"name":"com.","type":6,"TTL":900,"data":"a.gtld-servers.net. nstld.verisign-grs.com. 1640040308 1800 900 604800 86400"}],"Comment":"Response from 2001:503:231d::2:30."}`)) + httpmock.RegisterResponder("GET", "https://cloudflare-dns.com/dns-query?name=testfallback.com.&type=A", + httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"testfallback.com.","type":1}],"Answer":[{"name":"testfallback.com.","type":1,"TTL":3080,"data":"67.225.146.248"}]}`)) + tests := []struct { name string fields fields @@ -58,6 +63,18 @@ func TestDNSProxy_getResponse(t *testing.T) { want *dns.Msg wantErr bool }{ + {name: "test fallback", + fields: fields{Cache: &auditCache}, + args: args{requestMsg: &dns.Msg{Question: []dns.Question{{Name: "testfallback.com.", Qtype: dns.TypeA}}}}, + want: &dns.Msg{Answer: []dns.RR{rrDnsFallback}}, + wantErr: false, + }, + {name: "type A cloudflare-dns.com.", + fields: fields{Cache: &auditCache}, + args: args{requestMsg: &dns.Msg{Question: []dns.Question{{Name: "cloudflare-dns.com.", Qtype: dns.TypeA}}}}, + want: &dns.Msg{Answer: []dns.RR{rrDnsCloudflare}}, + wantErr: false, + }, {name: "type A dns.google", fields: fields{Cache: &auditCache}, args: args{requestMsg: &dns.Msg{Question: []dns.Question{{Name: "dns.google.", Qtype: dns.TypeA}}}}, diff --git a/firewall.go b/firewall.go index e8888f6..404c305 100644 --- a/firewall.go +++ b/firewall.go @@ -23,8 +23,6 @@ const ( target = "-j" accept = "ACCEPT" reject = "REJECT" - dnsServerIP = "8.8.8.8" - dnsServerIP2 = "8.8.4.4" classAPrivateAddressRange = "10.0.0.0/8" classBPrivateAddressRange = "172.16.0.0/12" classCPrivateAddressRange = "192.168.0.0/16" @@ -58,6 +56,8 @@ func addBlockRulesForGitHubHostedRunner(firewall *Firewall, endpoints []ipAddres func addBlockRules(firewall *Firewall, endpoints []ipAddressEndpoint, chain, netInterface, direction string) error { var ipt IPTables var err error + dnsServers := []string{"8.8.8.8", "8.8.4.4", "1.1.1.1"} + if firewall == nil { ipt, err = iptables.New() @@ -88,20 +88,13 @@ func addBlockRules(firewall *Firewall, endpoints []ipAddressEndpoint, chain, net } // Agent uses HTTPs to resolve domain names - // Allow 8.8.8.8 for dns - err = ipt.Append(filterTable, chain, direction, netInterface, protocol, tcp, - destination, dnsServerIP, target, accept) - - if err != nil { - return errors.Wrap(err, "failed to add rule") - } - - // Allow 8.8.4.4 for dns - err = ipt.Append(filterTable, chain, direction, netInterface, protocol, tcp, - destination, dnsServerIP2, target, accept) + for _, dnsServer := range dnsServers { + err = ipt.Append(filterTable, chain, direction, netInterface, protocol, tcp, + destination, dnsServer, target, accept) - if err != nil { - return errors.Wrap(err, "failed to add rule") + if err != nil { + return errors.Wrapf(err, "failed to add rule for DNS server %s", dnsServer) + } } // Allow AzureIPAddress