Skip to content

Conversation

robingustafsson
Copy link
Contributor

@robingustafsson robingustafsson commented Sep 25, 2017

This PR adds TLS version, cipher suite and OCSP stapled response information to each HTTP response object, implementing #298.

Example usage:

import http from "k6/http";
import { check } from "k6";

export let options = {
    tlsCipherSuites: [
        "TLS_RSA_WITH_RC4_128_SHA",
        "TLS_RSA_WITH_AES_128_GCM_SHA256",
    ],
    tlsVersion: {
        min: "ssl3.0",
        max: "tls1.2"
    }
};

export default function() {
    let res = http.get("https://sha256.badssl.com");
    check(res, {
        "is TLSv1.2": (r) => r.tls_version === "tls1.2",
        "is sha256 cipher suite": (r) => r.tls_cipher_suite === "TLS_RSA_WITH_AES_128_GCM_SHA256"
    });
};

and

import http from "k6/http";
import { check } from "k6";

export default function() {
    let res = http.get("https://stackoverflow.com");
    check(res, {
        "is OCSP response good": (r) => r.ocsp.stapled_response.status === "good"
    });
};

The OCSP stapled_response object also contains the following properties:

  • ocsp.stapled_response.revocation_reason: a string
  • ocsp.stapled_response.produced_at: a date string
  • ocsp.stapled_response.this_update: a date string
  • ocsp.stapled_response.next_update: a date string
  • ocsp.stapled_response.revoked_at: a date string

@robingustafsson
Copy link
Contributor Author

@liclac
Copy link
Contributor

liclac commented Oct 4, 2017

I want to make a structural change here, because for the first time in k6' history, we have a use for constants.

Making this easy-to-read string comparisons (ocsp.status === "good") sounds good in theory, until someone typo's it as "god" and suddenly we're refusing to trust any authority short of a holy scripture. And while that's a fairly easy one to catch, what about "tls1.2" vs "TLS1.2"?

The change I'm proposing is:

  1. Change the module definition from simply type HTTP struct{}, to:

    type HTTP struct{
        SSL_3_0, TLS_1_0, TLS_1_1, TLS_1_2 string
    }
    
    func New() *HTTP {
        return &HTTP{
            SSL_3_0: "ssl3.0",
            // ...
        }
    }
  2. Change js/modules/index.go from &http.HTTP{} to http.New(), and for consistency, also do this for all modules + their unit tests.

The actual naming of the constants is up for debate, and I think OCSP status may be better implemented as a tristate bool (*bool, true/false/null).

"sync"
"time"

"golang.org/x/crypto/ocsp"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please merge this into the import block below.

}

type OCSP struct {
StapledResponse OCSPStapledResponse
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this indirection really needed...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, maybe it should just be res.ocsp_response on the JS side?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe even just res.ocsp?

tags["proto"] = resp.Proto

if res.TLS != nil {
tlsState := res.TLS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can shorten this down to if tlsState := res.TLS; tlsState != nil {.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 typical Go noob mistake 😄

resp.TLSVersion = "tls1.2"
}
resp.TLSCipherSuite = lib.SupportedTLSCipherSuitesToString[tlsState.CipherSuite]
resp.OCSP.StapledResponse = OCSPStapledResponse{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant.


if res.TLS != nil {
tlsState := res.TLS
switch tlsState.Version {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a helper function, then assign resp.OCSP.StapledResponse to a fully initialized object. These switches are real lengthy.

resp.OCSP.StapledResponse.ProducedAt = ocspRes.ProducedAt.String()
resp.OCSP.StapledResponse.ThisUpdate = ocspRes.ThisUpdate.String()
resp.OCSP.StapledResponse.NextUpdate = ocspRes.NextUpdate.String()
resp.OCSP.StapledResponse.RevokedAt = ocspRes.RevokedAt.String()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in a format we can compare on the JS side... UNIX timestamps?

(I'm also kind of questioning what purpose it will serve for a load test script; I suppose it could be useful for logging, but not much more.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, should we use JS Date objects or Epoch milliseconds (to compare with Date.now() and Date.getTime()) when exposing to JS? Had the same question in the cookie PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just in the interest of performance, I think maybe giving a UNIX timestamp would be better than instancing whole date objects every time. The JS-native way seems to be to deal with milliseconds for them, so then you can just do new Date(revokedAt).

_, err := common.RunString(rt, `http.get("https://expired.badssl.com/");`)
assert.EqualError(t, err, "GoError: Get https://expired.badssl.com/: x509: certificate has expired or is not yet valid")
})
t.Run("tls10", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These versions are nigh identical and would do better templated than copypasted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will look into that.

@liclac
Copy link
Contributor

liclac commented Oct 4, 2017

I'd also like TLS version and OCSP status to be added as tags to responses.

@robingustafsson
Copy link
Contributor Author

@liclac Haha, ocsp.status === "god" is of course the status when you've successfully infiltrated a CA with a HTTP request 😄

Agree on the use of constants for TLS version and cipher suites. I'll change the OCSP status to use constants as well. IMHO that's more clear when reading than a tristate.

@robingustafsson
Copy link
Contributor Author

@liclac Requested changes have now been done, please have another look.

case tls.VersionTLS12:
res.TLSVersion = h.TLS_1_2
}
res.TLSCipherSuite = lib.SupportedTLSCipherSuitesToString[tlsState.CipherSuite]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newline, please.

res.TLSVersion = h.TLS_1_2
}
res.TLSCipherSuite = lib.SupportedTLSCipherSuitesToString[tlsState.CipherSuite]
ocspStapledRes := OCSP{Status: h.OCSP_STATUS_UNKNOWN}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newline, please.

func (res *HTTPResponse) setTLSInfo(h *HTTP, tlsState *tls.ConnectionState) {
switch tlsState.Version {
case tls.VersionSSL30:
res.TLSVersion = h.SSL_3_0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These constants should honestly be global consts, passing in the HTTP object just to read them feels silly.

Duration, Blocked, LookingUp, Connecting, Sending, Waiting, Receiving float64
}

type HTTPResponse struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This struct is honestly getting big enough that I'd split it off into a response.go file.

tags["status"] = strconv.Itoa(resp.Status)
tags["proto"] = resp.Proto

if tlsState := res.TLS; res.TLS != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tlsState is longer than res.TLS. As far as shorthands go, that's pretty lousy.

@robingustafsson
Copy link
Contributor Author

@liclac Requested changes have been made.

@liclac liclac merged commit c849005 into master Oct 24, 2017
@liclac liclac deleted the feature/tls-resp-info branch October 24, 2017 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants