Hi I am interested in adding certificates to the certPool while the server is running, but it seems like it's not picked up. Do I need to relaunch the server for this to work?
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
handler := http.NewServeMux()
// verify the cert if given (or check for jwt token)
handler.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
if certs := r.TLS.PeerCertificates; len(certs) > 0 {
cert := certs[0]
fmt.Println(cert.Subject.CommonName)
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Cert Valid")
return
}
// ...
})
// the cert pool that holds the client CAs
certPool := x509.NewCertPool()
// post a new CA and add it to the pool
handler.HandleFunc("/ca", func(rw http.ResponseWriter, r *http.Request) {
caCertFile, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("error reading CA certificate: %v", err)
rw.WriteHeader(http.StatusUnprocessableEntity)
return
}
certPool.AppendCertsFromPEM(caCertFile)
rw.WriteHeader(http.StatusCreated)
})
server := http.Server{
Addr: ":9090",
Handler: handler,
TLSConfig: &tls.Config{
ClientAuth: tls.VerifyClientCertIfGiven,
ClientCAs: certPool,
MinVersion: tls.VersionTLS12,
},
}
if err := server.ListenAndServeTLS("certs/server/tls.crt", "certs/server/tls.key"); err != nil {
log.Fatalf("error listening to port: %v", err)
}
}
I am posting the cert with curl
curl -k -X POST https://localhost:9090/ca -d #test-ca.pem
Based, on one comment, I have tried the below but it doesn't seem to work either.
certFile, err := ioutil.ReadFile("certs/server/tls.crt")
if err != nil {
log.Fatal(err)
}
keyFile, err := ioutil.ReadFile("certs/server/tls.key")
if err != nil {
log.Fatal(err)
}
cert, err := tls.X509KeyPair(certFile, keyFile)
if err != nil {
log.Fatal(err)
}
listener, err := tls.Listen("tcp", ":9090", &tls.Config{
ClientAuth: tls.VerifyClientCertIfGiven,
ClientCAs: certPool,
Certificates: []tls.Certificate{cert},
})
if err != nil {
log.Fatalf("error creating listener: %v", err)
}
if err = http.Serve(listener, handler); err != nil {
log.Fatalf("error serving: %v", err)
}
Related
I’m trying to create a ssh server in go using the x/crypto/ssh module but i can’t manage to make the public key authentification work.
I tried the ExampleNewServerConn() function in the ssh/example_test.go file (in the https://go.googlesource.com/crypto repo) but the public key method doesn’t work, it looks like the server isn’t advertising the right algorithms because i get this line when trying to connect with a ssh client :
debug1: send_pubkey_test: no mutual signature algorithm
If i add -o PubkeyAcceptedKeyTypes=+ssh-rsa the public key login works, but this rsa method is deprecated, i would like to use another public key type, how can i do that ?
Thanks in advance.
Edit : here is the code that i used to test
package main
import (
"fmt"
"io/ioutil"
"log"
"net"
"golang.org/x/crypto/ssh"
terminal "golang.org/x/term"
)
func main() {
authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
if err != nil {
log.Fatalf("Failed to load authorized_keys, err: %v", err)
}
authorizedKeysMap := map[string]bool{}
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
log.Fatal(err)
}
authorizedKeysMap[string(pubKey.Marshal())] = true
authorizedKeysBytes = rest
}
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if c.User() == "testuser" && string(pass) == "tiger" {
return nil, nil
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if authorizedKeysMap[string(pubKey.Marshal())] {
return &ssh.Permissions{
// Record the public key used for authentication.
Extensions: map[string]string{
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
},
}
privateBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
log.Fatal("Failed to load private key: ", err)
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
log.Fatal("Failed to parse private key: ", err)
}
config.AddHostKey(private)
listener, err := net.Listen("tcp", "0.0.0.0:2022")
if err != nil {
log.Fatal("failed to listen for connection: ", err)
}
nConn, err := listener.Accept()
if err != nil {
log.Fatal("failed to accept incoming connection: ", err)
}
conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
if err != nil {
log.Fatal("failed to handshake: ", err)
}
log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
log.Fatalf("Could not accept channel: %v", err)
}
go func(in <-chan *ssh.Request) {
for req := range in {
req.Reply(req.Type == "shell", nil)
}
}(requests)
term := terminal.NewTerminal(channel, "> ")
go func() {
defer channel.Close()
for {
line, err := term.ReadLine()
if err != nil {
break
}
fmt.Println(line)
}
}()
}
}
I found why the client and the server can’t communicate, the rsa-sha2 algorithms are not yet implemented in the x/crypto library. There is an issue about it on github : https://github.com/golang/go/issues/49952 .
A temporary solution is to add
replace golang.org/x/crypto => github.com/rmohr/crypto v0.0.0-20211203105847-e4ed9664ac54
at the end of your go.mod file, it uses a x/crypto fork from #rmohr that works with rsa-sha2.
This is the easy way to do it, let letsencrypt handle the certificates for you :)
func main() {
r := mux.NewRouter()
r.HandleFunc("/index", index)
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("www.example.com"), // replace with your domain
Cache: autocert.DirCache("certs"),
}
srv := &http.Server{
Handler: r,
Addr: ":https",
WriteTimeout: 5 * time.Second,
ReadTimeout: 5 * time.Second,
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
},
}
go http.ListenAndServe(":http", certManager.HTTPHandler(nil)) //nolint
log.Fatal(srv.ListenAndServeTLS("", ""))
}
I am trying to do the following.
|Upload file in HTML post file form|
|
⌄
|Server A forwards the multipart request|
|
⌄
|Server B receives and stores the file from the forwarded multipart request|
|
⌄
|Server A receives response from Server B when Server B is done|
Processing the multipart request on Server A is straightforward, but when I try to process the forwarded request on Server B it fails with multipart: NextPart: EOF.
I am trying to create separate frontend/backend services. Frontend only handles UI related processing, while backend will actually do some processing on the file, hence the multipart request forwarding needed.
The forwarding code on Server A is as follows.
The solution has been taken from here.
https://stackoverflow.com/a/34725635/6569715
func forwardRequest(address string, path string, r *http.Request) (interface{}, error) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
r.Body = ioutil.NopCloser(bytes.NewReader(body))
proxyReq, err := http.NewRequest(r.Method, fmt.Sprintf("%s%s", address, path), bytes.NewReader(body))
if err != nil {
return nil, err
}
for header, values := range r.Header {
for _, value := range values {
proxyReq.Header.Add(header, value)
}
}
client := &http.Client{}
resp, err := client.Do(proxyReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return resp, nil
}
And the code on Server B to process the forwarded request:
func testMultiPart(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(10 << 20); err != nil {
err = errors.Wrap(errors.WithStack(err), "Backend: Failed to parse form")
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, fmt.Sprintf("{\"error\":\"%s\"}", err.Error())
return
}
}
Any help is appreciated.
I managed to make it work. I believe it was just my own mistake not filling in the URI properly. In any case I will post my snippets from my solution for future reference.
The client html file form part:
<form action="/test-main/file-test" enctype="multipart/form-data" method="post">
<label for="file-upload">Upload your file :</label>
<input type="file" id="file-upload" name="file-upload" accept="image/*">
</form>
Server A code:
import (
"net/http"
"errors"
"fmt"
"log"
"io/ioutil"
"bytes"
"github.com/gorilla/mux"
)
func fileUpload(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return log.Fatal(err)
}
r.Body = ioutil.NopCloser(bytes.NewReader(body))
// If Server A and B are separate docker images, you may need to use their docker subnet IP, like below.
proxyReq, err := http.NewRequest(r.Method, fmt.Sprintf("http://172.18.0.2:8082%s", r.RequestURI), bytes.NewReader(body))
if err != nil {
return log.Fatal(err)
}
for header, values := range r.Header {
for _, value := range values {
proxyReq.Header.Add(header, value)
}
}
client := &http.Client{}
resp, err := client.Do(proxyReq)
if err != nil {
return log.Fatal(err)
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return log.Fatal(err)
}
// Process Server B response
// ...
}
func createRouter() *mux.Router {
r := mux.NewRouter()
testPath := r.PathPrefix("/test-main").Subrouter()
testPath.HandleFunc("/file-test", fileUpload)
return r
}
func main() {
// Create Server and Route Handlers
srv := &http.Server{
Handler: createRouter(),
Addr: ":8081",
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
}
// Start Server
go func() {
log.Println("Starting Server")
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}()
}
And Server B code:
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
)
func uploadFile(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(10 << 20); err != nil {
log.Fatal(err)
}
file, handler, err := r.FormFile("file-upload")
if err == http.ErrMissingFile {
return nil
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("Uploaded File: %+v\n", handler.Filename)
fmt.Printf("File Size: %+v\n", handler.Size)
fmt.Printf("MIME Header: %+v\n", handler.Header)
defer file.Close()
// Create file
dst, err := os.Create(fmt.Sprintf("/some-destination-folder/%s", handler.Filename))
if err != nil {
log.Fatal(err)
}
// Copy the uploaded file to the created file on the file system.
if _, err := io.Copy(dst, file); err != nil {
if err2 := dst.Close(); err2 != nil {
log.Fatal(err)
}
log.Fatal(err)
}
dst.Close()
return nil
}
func (c *Controller) createRouter() *mux.Router {
r := mux.NewRouter()
testPath := r.PathPrefix("/test-main").Subrouter()
testPath.HandleFunc("/file-test", uploadFile)
return r
}
func main() {
// Create Server and Route Handlers
srv := &http.Server{
Handler: createRouter(),
Addr: ":8082",
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
}
// Start Server
go func() {
log.Println("Starting Server")
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}()
}
Good luck for future readers.
I'm trying to write a custom VerifyPeerCertificate to get the certificate even if CN and FQDN do not match.
I'm new to golang, so I'm trying to modify some code that I've found, and make it work but without any success.
So here is my code :
package main
import (
"fmt"
"log"
"crypto/tls"
"crypto/x509"
)
func main() {
customVerify := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
roots := x509.NewCertPool()
for _, rawCert := range rawCerts {
c, _ := x509.ParseCertificate(rawCert)
roots.AddCert(c)
}
cert, _ := x509.ParseCertificate(rawCerts[0])
fmt.Println("subject name is : ",cert.Subject.CommonName)
opts := x509.VerifyOptions{
DNSName: cert.Subject.CommonName,
Roots: roots,
}
if _, err := cert.Verify(opts); err != nil {
panic("failed to verify certificate: " + err.Error())
return err
}
return nil
}
log.SetFlags(log.Lshortfile)
conf := &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: customVerify,
}
conn, err := tls.Dial("tcp", "127.0.0.1:9007", conf)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
n, err := conn.Write([]byte("hello\n"))
if err != nil {
log.Println(n, err)
return
}
buf := make([]byte, 100)
n, err = conn.Read(buf)
if err != nil {
log.Println(n, err)
return
}
println(string(buf[:n]))
}
I'm trying to get the certificate of a local server.
when I try to run the code, I'm getting this error :
root#mymachine:~/Tproject# go run test.go
subject name is : dssdemo
test.go:50: remote error: tls: bad certificate
I've tried to mimic the example_Certificate_Verify
Can someone help me with this ?
Thank you in advance.
Edit:
Mutual HTTPS is causing the : test.go:50: remote error: tls: bad certificate
But still, Is it possible to somehow return the server certificate ?
This custom verification ignore all verfications:
func ipSCert(host, port string) ([]*x509.Certificate, string, error) {
var ipcertchain []*x509.Certificate
customVerify := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
roots := x509.NewCertPool()
for _, rawCert := range rawCerts {
c, _ := x509.ParseCertificate(rawCert)
certItem, _ := x509.ParseCertificate(rawCert)
ipcertchain = append(ipcertchain, certItem)
roots.AddCert(c)
}
return nil
}
log.SetFlags(log.Lshortfile)
d := &net.Dialer{
Timeout: time.Duration(TimeoutSeconds) * time.Second,
}
cs, err := cipherSuite()
if err != nil {
return []*x509.Certificate{&x509.Certificate{}}, "", err
}
conf := &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: customVerify,
CipherSuites: cs,
MaxVersion: tlsVersion(),
}
conn, err := tls.DialWithDialer(d, "tcp", host+":"+port, conf)
if err != nil {
return nil, "", err
}
conn.Close()
return ipcertchain, host, nil
}
I am trying to SSH to a Cisco wireless controller through Go, using Go's golang.org/x/crypto/ssh library, to programmatically configure access points. The problem I'm running into is correctly parsing the controller CLI in Go. For example, this is the typical SSH login to the controller:
$ ssh <controller_ip>
(Cisco Controller)
User: username
Password:****************
(Cisco Controller) >
I am trying to figure out how to send the username and then the password after the SSH session is established in Go. So far, I am able to successfully SSH to the controller, but the program exits at the username prompt, like this:
$ go run main.go
(Cisco Controller)
User:
How would I go about sending the username when prompted, then repeating that for the password prompt?
No errors are being thrown or exit codes are being given, so I'm not sure why the program is exiting immediately at the username prompt. But Even if it wasn't exiting that way, I'm still unsure of how to send the username and password when the controller's CLI is expecting it.
Here is my code:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"io/ioutil"
"os"
"strings"
"path/filepath"
"bufio"
"fmt"
"errors"
"time"
)
const (
HOST = "host"
)
func main() {
hostKey, err := checkHostKey(HOST)
if err != nil {
log.Fatal(err)
}
key, err := ioutil.ReadFile("/Users/user/.ssh/id_rsa")
if err != nil {
log.Fatalf("unable to read private key: %v", err)
}
// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("unable to parse private key: %v", err)
}
// Create client config
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
Timeout: time.Second * 5,
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", HOST+":22", config)
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
// Create a session
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := session.StdoutPipe()
if err != nil {
log.Fatal(err)
}
modes := ssh.TerminalModes{
ssh.ECHO: 0,
ssh.TTY_OP_ISPEED: 9600,
ssh.TTY_OP_OSPEED: 9600,
}
if err := session.RequestPty("xterm", 0, 200, modes); err != nil {
log.Fatal(err)
}
if err := session.Shell(); err != nil {
log.Fatal(err)
}
buf := make([]byte, 1000)
n, err := stdout.Read(buf) //this reads the ssh terminal welcome message
loadStr := ""
if err == nil {
loadStr = string(buf[:n])
}
for (err == nil) && (!strings.Contains(loadStr, "(Cisco Controller)")) {
n, err = stdout.Read(buf)
loadStr += string(buf[:n])
}
fmt.Println(loadStr)
if _, err := stdin.Write([]byte("show ap summary\r")); err != nil {
panic("Failed to run: " + err.Error())
}
}
func checkHostKey(host string) (ssh.PublicKey, error) {
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
return nil, errors.New(fmt.Sprintf("error parsing %q: %v", fields[2], err))
}
break
}
}
if hostKey == nil {
return nil, errors.New(fmt.Sprintf("no hostkey for %s", host))
}
return hostKey, nil
}
Finally got it working. Here is my new code inspired by this post:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"io/ioutil"
"os"
"strings"
"path/filepath"
"bufio"
"fmt"
"errors"
"time"
)
func main() {
client, err := authenticate("10.4.112.11", "mwalto7", "lion$Tiger$Bear$")
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
// Create a session
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
log.Fatal(err)
}
session.Stdout = os.Stdout
session.Stderr = os.Stderr
if err := session.Shell(); err != nil {
log.Fatal(err)
}
for _, cmd := range os.Args[1:] {
stdin.Write([]byte(cmd + "\n"))
}
stdin.Write([]byte("logout\n"))
stdin.Write([]byte("N\n"))
session.Wait()
}
func authenticate(host, username, password string) (ssh.Client, error) {
hostKey, err := checkHostKey(host)
if err != nil {
log.Fatal(err)
}
key, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa"))
if err != nil {
log.Fatalf("unable to read private key: %v", err)
}
// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("unable to parse private key: %v", err)
}
// Create client config
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(password),
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
Timeout: time.Second * 5,
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", host+":22", config)
return *client, err
}
func checkHostKey(host string) (ssh.PublicKey, error) {
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
return nil, errors.New(fmt.Sprintf("error parsing %q: %v", fields[2], err))
}
break
}
}
if hostKey == nil {
return nil, errors.New(fmt.Sprintf("no hostkey for %s", host))
}
return hostKey, nil
}
I have more than one ip to go to the internet. I am making request choosing interface. In this case how should I set headers?
tcpAddr := &net.TCPAddr{
IP: addrs[3].(*net.IPNet).IP, // Choosing ip address number 3
}
d := net.Dialer{LocalAddr: tcpAddr}
conn, err2 := d.Dial("tcp", "www.whatismyip.com:80")
if err2 != nil {
log.Fatal(err2)
}
defer conn.Close()
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{LocalAddr: tcpAddr}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
client := &http.Client{
Transport: transport,
}
response, err := client.Get("https://www.whatismyip.com/")
Usually headers are set in this way:
req.Header.Set("name", "value")
But cannot figure out how to set them to my code.
I guess they must be set somewhere in http.Transport or http.Client. But how exactly?
My full code:
package main
import (
"bytes"
"fmt"
"github.com/PuerkitoBio/goquery"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"time"
)
func main() {
ief, err := net.InterfaceByName("eth0")
if err != nil {
log.Fatal(err)
}
addrs, err := ief.Addrs()
if err != nil {
log.Fatal(err)
}
tcpAddr := &net.TCPAddr{
IP: addrs[3].(*net.IPNet).IP, // Choosing ip address number 3
}
d := net.Dialer{LocalAddr: tcpAddr}
conn, err2 := d.Dial("tcp", "www.whatismyip.com:80")
if err2 != nil {
log.Fatal(err2)
}
defer conn.Close()
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{LocalAddr: tcpAddr}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
client := &http.Client{
Transport: transport,
}
response, err := client.Get("https://www.whatismyip.com/")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
}
var contentsStr = string(contents)
fmt.Printf("%s\n", contentsStr)
var doc = DocByHtmlString(contentsStr)
doc.Find("div").Each(func(i int, s *goquery.Selection) {
attr, exists := s.Attr("class")
if exists {
if attr == "ip" {
fmt.Println(s.Text())
}
}
})
}
}
func DocByHtmlString(html string) *goquery.Document {
doc, err := goquery.NewDocumentFromReader(bytes.NewBufferString(html))
if err != nil {
panic(err)
}
return doc
}
Create a request:
req, err := http.NewRequest("GET", "https://www.whatismyip.com/", nil)
if err != nil {
// handle error
}
Set the headers:
req.Header.Set("name", "value")
Run the request using client as configured in the question:
resp, err := client.Do(req)
if err != nil {
// handle error
}
Handle the response as shown in the question.