diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go
index 470303938e50e368f6deef29f3309c64510f4b85..92068934d82d3b9dcaee127544c86f39012d2b04 100644
--- a/cmd/dex/serve.go
+++ b/cmd/dex/serve.go
@@ -6,7 +6,6 @@ import (
 	"crypto/x509"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -76,7 +75,7 @@ func commandServe() *cobra.Command {
 
 func runServe(options serveOptions) error {
 	configFile := options.config
-	configData, err := ioutil.ReadFile(configFile)
+	configData, err := os.ReadFile(configFile)
 	if err != nil {
 		return fmt.Errorf("failed to read config file %s: %v", configFile, err)
 	}
@@ -148,7 +147,7 @@ func runServe(options serveOptions) error {
 		if c.GRPC.TLSClientCA != "" {
 			// Parse certificates from client CA file to a new CertPool.
 			cPool := x509.NewCertPool()
-			clientCert, err := ioutil.ReadFile(c.GRPC.TLSClientCA)
+			clientCert, err := os.ReadFile(c.GRPC.TLSClientCA)
 			if err != nil {
 				return fmt.Errorf("invalid config: reading from client CA file: %v", err)
 			}
diff --git a/connector/atlassiancrowd/atlassiancrowd.go b/connector/atlassiancrowd/atlassiancrowd.go
index 6f03406086adaf3585d1f6228d64f5c3c40546f1..e2ca94b0de9aa6fcef008d6b045fcd76c51bf0ff 100644
--- a/connector/atlassiancrowd/atlassiancrowd.go
+++ b/connector/atlassiancrowd/atlassiancrowd.go
@@ -7,7 +7,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"strings"
@@ -432,7 +431,7 @@ func (c *crowdConnector) crowdUserManagementRequest(ctx context.Context, method
 
 // validateCrowdResponse validates unique not JSON responses from API
 func (c *crowdConnector) validateCrowdResponse(resp *http.Response) ([]byte, error) {
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, fmt.Errorf("crowd: read user body: %v", err)
 	}
diff --git a/connector/atlassiancrowd/atlassiancrowd_test.go b/connector/atlassiancrowd/atlassiancrowd_test.go
index 3559445d213ce4b5a6c669260ea88014807167e7..36789a391971168ff273806606581b06eb168f72 100644
--- a/connector/atlassiancrowd/atlassiancrowd_test.go
+++ b/connector/atlassiancrowd/atlassiancrowd_test.go
@@ -6,7 +6,7 @@ import (
 	"crypto/tls"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"reflect"
@@ -152,7 +152,7 @@ func newTestCrowdConnector(baseURL string) crowdConnector {
 	connector := crowdConnector{}
 	connector.BaseURL = baseURL
 	connector.logger = &logrus.Logger{
-		Out:       ioutil.Discard,
+		Out:       io.Discard,
 		Level:     logrus.DebugLevel,
 		Formatter: &logrus.TextFormatter{DisableColors: true},
 	}
diff --git a/connector/bitbucketcloud/bitbucketcloud.go b/connector/bitbucketcloud/bitbucketcloud.go
index e81893da07e29f71826c5f12b430eb706dc8df82..b9134e919f8228f006494fa5402c236a4913bf56 100644
--- a/connector/bitbucketcloud/bitbucketcloud.go
+++ b/connector/bitbucketcloud/bitbucketcloud.go
@@ -6,7 +6,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"sync"
 	"time"
@@ -453,7 +453,7 @@ func get(ctx context.Context, client *http.Client, apiURL string, v interface{})
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			return fmt.Errorf("bitbucket: read body: %s: %v", resp.Status, err)
 		}
diff --git a/connector/gitea/gitea.go b/connector/gitea/gitea.go
index 33cc3126e6002f5b6c29c8390deb58af5353e54f..cd371d37416319799a61bc103b20cded9640363f 100644
--- a/connector/gitea/gitea.go
+++ b/connector/gitea/gitea.go
@@ -6,7 +6,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strconv"
 	"sync"
@@ -252,7 +252,7 @@ func (c *giteaConnector) user(ctx context.Context, client *http.Client) (giteaUs
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			return u, fmt.Errorf("gitea: read body: %v", err)
 		}
diff --git a/connector/github/github.go b/connector/github/github.go
index 02f2cae8048967cc2fc944303ee689254efc8fef..ef8d418fa8b8e54e8cd4c5484e1abdcbd715db0e 100644
--- a/connector/github/github.go
+++ b/connector/github/github.go
@@ -8,9 +8,10 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
+	"os"
 	"regexp"
 	"strconv"
 	"strings"
@@ -210,7 +211,7 @@ func (e *oauth2Error) Error() string {
 // newHTTPClient returns a new HTTP client that trusts the custom declared rootCA cert.
 func newHTTPClient(rootCA string) (*http.Client, error) {
 	tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
-	rootCABytes, err := ioutil.ReadFile(rootCA)
+	rootCABytes, err := os.ReadFile(rootCA)
 	if err != nil {
 		return nil, fmt.Errorf("failed to read root-ca: %v", err)
 	}
@@ -488,7 +489,7 @@ func get(ctx context.Context, client *http.Client, apiURL string, v interface{})
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			return "", fmt.Errorf("github: read body: %v", err)
 		}
diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go
index e40601402dd07872b6e90645f22bfce7ca1a6b75..7d8e83377fa283212cd27f8aa8854bee012bf495 100644
--- a/connector/gitlab/gitlab.go
+++ b/connector/gitlab/gitlab.go
@@ -6,7 +6,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strconv"
 
@@ -232,7 +232,7 @@ func (c *gitlabConnector) user(ctx context.Context, client *http.Client) (gitlab
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			return u, fmt.Errorf("gitlab: read body: %v", err)
 		}
@@ -266,7 +266,7 @@ func (c *gitlabConnector) userGroups(ctx context.Context, client *http.Client) (
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			return nil, fmt.Errorf("gitlab: read body: %v", err)
 		}
diff --git a/connector/google/google.go b/connector/google/google.go
index eccb1fc7d77ac9b33c9e173c38a685d86e33b6a4..953cb32edfd423586feb66f9fad654e293d6c71d 100644
--- a/connector/google/google.go
+++ b/connector/google/google.go
@@ -5,8 +5,8 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net/http"
+	"os"
 	"time"
 
 	"github.com/coreos/go-oidc/v3/oidc"
@@ -274,7 +274,7 @@ func createDirectoryService(serviceAccountFilePath string, email string) (*admin
 	if serviceAccountFilePath == "" || email == "" {
 		return nil, fmt.Errorf("directory service requires both serviceAccountFilePath and adminEmail")
 	}
-	jsonCredentials, err := ioutil.ReadFile(serviceAccountFilePath)
+	jsonCredentials, err := os.ReadFile(serviceAccountFilePath)
 	if err != nil {
 		return nil, fmt.Errorf("error reading credentials from file: %v", err)
 	}
diff --git a/connector/keystone/keystone.go b/connector/keystone/keystone.go
index 3750710e742af2d4e8a80facf674b613253e7f49..c4e98d05693754195ef4028b31678c1ad6e2ac5d 100644
--- a/connector/keystone/keystone.go
+++ b/connector/keystone/keystone.go
@@ -6,7 +6,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 
 	"github.com/dexidp/dex/connector"
@@ -133,7 +133,7 @@ func (p *conn) Login(ctx context.Context, scopes connector.Scopes, username, pas
 		return identity, false, nil
 	}
 	token := resp.Header.Get("X-Subject-Token")
-	data, err := ioutil.ReadAll(resp.Body)
+	data, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return identity, false, err
 	}
@@ -260,7 +260,7 @@ func (p *conn) getUser(ctx context.Context, userID string, token string) (*userR
 		return nil, err
 	}
 
-	data, err := ioutil.ReadAll(resp.Body)
+	data, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
@@ -290,7 +290,7 @@ func (p *conn) getUserGroups(ctx context.Context, userID string, token string) (
 		return nil, err
 	}
 
-	data, err := ioutil.ReadAll(resp.Body)
+	data, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
diff --git a/connector/keystone/keystone_test.go b/connector/keystone/keystone_test.go
index b138012426e219d88f7c13ace1c324a3c324e735..cf007f19c38b2e1c7d4874579445de8f3ef08ee2 100644
--- a/connector/keystone/keystone_test.go
+++ b/connector/keystone/keystone_test.go
@@ -5,7 +5,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"reflect"
@@ -78,7 +78,7 @@ func getAdminToken(t *testing.T, adminName, adminPass string) (token, id string)
 
 	token = resp.Header.Get("X-Subject-Token")
 
-	data, err := ioutil.ReadAll(resp.Body)
+	data, err := io.ReadAll(resp.Body)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -122,7 +122,7 @@ func createUser(t *testing.T, token, userName, userEmail, userPass string) strin
 		t.Fatal(err)
 	}
 
-	data, err := ioutil.ReadAll(resp.Body)
+	data, err := io.ReadAll(resp.Body)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -183,7 +183,7 @@ func createGroup(t *testing.T, token, description, name string) string {
 		t.Fatal(err)
 	}
 
-	data, err := ioutil.ReadAll(resp.Body)
+	data, err := io.ReadAll(resp.Body)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go
index eaee078d371d92a14acd8d2fbfd80a3137e98b22..c9447d471aba5fe0764bb7b032539b07acedf01a 100644
--- a/connector/ldap/ldap.go
+++ b/connector/ldap/ldap.go
@@ -7,8 +7,8 @@ import (
 	"crypto/x509"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"net"
+	"os"
 
 	"github.com/go-ldap/ldap/v3"
 
@@ -257,7 +257,7 @@ func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) {
 		data := c.RootCAData
 		if len(data) == 0 {
 			var err error
-			if data, err = ioutil.ReadFile(c.RootCA); err != nil {
+			if data, err = os.ReadFile(c.RootCA); err != nil {
 				return nil, fmt.Errorf("ldap: read ca file: %v", err)
 			}
 		}
diff --git a/connector/ldap/ldap_test.go b/connector/ldap/ldap_test.go
index 9ae45674314d0e564b951ed89e823f90b9755374..83f9f4790ca73a0c2b1fc907f4b54f03b7352970 100644
--- a/connector/ldap/ldap_test.go
+++ b/connector/ldap/ldap_test.go
@@ -3,7 +3,7 @@ package ldap
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 	"testing"
 
@@ -555,7 +555,7 @@ func runTests(t *testing.T, connMethod connectionMethod, config *Config, tests [
 	c.BindDN = "cn=admin,dc=example,dc=org"
 	c.BindPW = "admin"
 
-	l := &logrus.Logger{Out: ioutil.Discard, Formatter: &logrus.TextFormatter{}}
+	l := &logrus.Logger{Out: io.Discard, Formatter: &logrus.TextFormatter{}}
 
 	conn, err := c.openConnector(l)
 	if err != nil {
diff --git a/connector/linkedin/linkedin.go b/connector/linkedin/linkedin.go
index 1c8312c11e9b4ca4eebfba34e3d498f47e4e2421..f79f1c49d8a57723b55809b08dde6c017c1fcd30 100644
--- a/connector/linkedin/linkedin.go
+++ b/connector/linkedin/linkedin.go
@@ -5,7 +5,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 
@@ -169,7 +169,7 @@ func (c *linkedInConnector) primaryEmail(ctx context.Context, client *http.Clien
 	}
 	defer resp.Body.Close()
 
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return email, fmt.Errorf("read body: %v", err)
 	}
@@ -209,7 +209,7 @@ func (c *linkedInConnector) profile(ctx context.Context, client *http.Client) (p
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			return p, fmt.Errorf("read body: %v", err)
 		}
diff --git a/connector/openshift/openshift.go b/connector/openshift/openshift.go
index f06e8f8045e818c4858122c181ea60a4c6f873a2..44a03edf39ef7855442f5aab061201476f6b03d3 100644
--- a/connector/openshift/openshift.go
+++ b/connector/openshift/openshift.go
@@ -6,9 +6,10 @@ import (
 	"crypto/x509"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
+	"os"
 	"strings"
 	"time"
 
@@ -195,7 +196,7 @@ func (c *openshiftConnector) user(ctx context.Context, client *http.Client) (u u
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			return u, fmt.Errorf("read body: %v", err)
 		}
@@ -223,7 +224,7 @@ func newHTTPClient(insecureCA bool, rootCA string) (*http.Client, error) {
 		tlsConfig = tls.Config{InsecureSkipVerify: true}
 	} else if rootCA != "" {
 		tlsConfig = tls.Config{RootCAs: x509.NewCertPool()}
-		rootCABytes, err := ioutil.ReadFile(rootCA)
+		rootCABytes, err := os.ReadFile(rootCA)
 		if err != nil {
 			return nil, fmt.Errorf("failed to read root-ca: %v", err)
 		}
diff --git a/connector/saml/saml.go b/connector/saml/saml.go
index 0d52b131161f7bbf7332cfb9a2c67096130b1ca5..908ec703c9753a39a51e924ac407acbbc63d253c 100644
--- a/connector/saml/saml.go
+++ b/connector/saml/saml.go
@@ -8,7 +8,7 @@ import (
 	"encoding/pem"
 	"encoding/xml"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"strings"
 	"sync"
 	"time"
@@ -194,7 +194,7 @@ func (c *Config) openConnector(logger log.Logger) (*provider, error) {
 
 		var caData []byte
 		if c.CA != "" {
-			data, err := ioutil.ReadFile(c.CA)
+			data, err := os.ReadFile(c.CA)
 			if err != nil {
 				return nil, fmt.Errorf("read ca file: %v", err)
 			}
diff --git a/connector/saml/saml_test.go b/connector/saml/saml_test.go
index 67d7efb14045f26d60f1d23bda150dec70947824..95d513ed19db27f1853c622adc963c2b09fe8e44 100644
--- a/connector/saml/saml_test.go
+++ b/connector/saml/saml_test.go
@@ -5,7 +5,7 @@ import (
 	"encoding/base64"
 	"encoding/pem"
 	"errors"
-	"io/ioutil"
+	"os"
 	"sort"
 	"testing"
 	"time"
@@ -392,7 +392,7 @@ func TestTamperedResponseNameID(t *testing.T) {
 }
 
 func loadCert(ca string) (*x509.Certificate, error) {
-	data, err := ioutil.ReadFile(ca)
+	data, err := os.ReadFile(ca)
 	if err != nil {
 		return nil, err
 	}
@@ -426,7 +426,7 @@ func (r responseTest) run(t *testing.T) {
 		t.Fatal(err)
 	}
 	conn.now = func() time.Time { return now }
-	resp, err := ioutil.ReadFile(r.respFile)
+	resp, err := os.ReadFile(r.respFile)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -456,11 +456,11 @@ func (r responseTest) run(t *testing.T) {
 
 func TestConfigCAData(t *testing.T) {
 	logger := logrus.New()
-	validPEM, err := ioutil.ReadFile("testdata/ca.crt")
+	validPEM, err := os.ReadFile("testdata/ca.crt")
 	if err != nil {
 		t.Fatal(err)
 	}
-	valid2ndPEM, err := ioutil.ReadFile("testdata/okta-ca.pem")
+	valid2ndPEM, err := os.ReadFile("testdata/okta-ca.pem")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -551,7 +551,7 @@ func runVerify(t *testing.T, ca string, resp string, shouldSucceed bool) {
 
 	validator := dsig.NewDefaultValidationContext(s)
 
-	data, err := ioutil.ReadFile(resp)
+	data, err := os.ReadFile(resp)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/examples/example-app/main.go b/examples/example-app/main.go
index e417c8b26fd84e8dc095b54b2e8f1b4ad7a42022..451bea5b46015c9c692b45d6a526a8fb9cfa53a1 100644
--- a/examples/example-app/main.go
+++ b/examples/example-app/main.go
@@ -8,7 +8,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -43,7 +42,7 @@ type app struct {
 // return an HTTP client which trusts the provided root CAs.
 func httpClientForRootCAs(rootCAs string) (*http.Client, error) {
 	tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
-	rootCABytes, err := ioutil.ReadFile(rootCAs)
+	rootCABytes, err := os.ReadFile(rootCAs)
 	if err != nil {
 		return nil, fmt.Errorf("failed to read root-ca: %v", err)
 	}
diff --git a/examples/grpc-client/client.go b/examples/grpc-client/client.go
index 7b3949a1c4109d6b15601ca0e8fb489434cfc938..c3a69097dcaff580e6e5937e959bc31d7aff65fb 100644
--- a/examples/grpc-client/client.go
+++ b/examples/grpc-client/client.go
@@ -6,8 +6,8 @@ import (
 	"crypto/x509"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
+	"os"
 
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
@@ -17,7 +17,7 @@ import (
 
 func newDexClient(hostAndPort, caPath, clientCrt, clientKey string) (api.DexClient, error) {
 	cPool := x509.NewCertPool()
-	caCert, err := ioutil.ReadFile(caPath)
+	caCert, err := os.ReadFile(caPath)
 	if err != nil {
 		return nil, fmt.Errorf("invalid CA crt file: %s", caPath)
 	}
diff --git a/server/deviceflowhandlers_test.go b/server/deviceflowhandlers_test.go
index 3898d4fc8eb2a6f66d0d0a5a33fe13d126007e44..c387af4337d0104c7b563c50d3ba78a79bfa3cdb 100644
--- a/server/deviceflowhandlers_test.go
+++ b/server/deviceflowhandlers_test.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -101,7 +101,7 @@ func TestHandleDeviceCode(t *testing.T) {
 				t.Errorf("Unexpected Response Type.  Expected %v got %v", tc.expectedResponseCode, rr.Code)
 			}
 
-			body, err := ioutil.ReadAll(rr.Body)
+			body, err := io.ReadAll(rr.Body)
 			if err != nil {
 				t.Errorf("Could read token response %v", err)
 			}
@@ -541,7 +541,7 @@ func TestDeviceTokenResponse(t *testing.T) {
 				t.Errorf("Unexpected Response Type.  Expected %v got %v", tc.expectedResponseCode, rr.Code)
 			}
 
-			body, err := ioutil.ReadAll(rr.Body)
+			body, err := io.ReadAll(rr.Body)
 			if err != nil {
 				t.Errorf("Could read token response %v", err)
 			}
diff --git a/server/server_test.go b/server/server_test.go
index 682d16a731a5a666ee71f25fa5a658590aa95d58..6f4bcb81aa2eac97454c0113d92ad862b5f6ee15 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -8,7 +8,7 @@ import (
 	"encoding/pem"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"net/http/httputil"
@@ -1588,7 +1588,7 @@ func TestOAuth2DeviceFlow(t *testing.T) {
 					t.Errorf("Could not request device code: %v", err)
 				}
 				defer resp.Body.Close()
-				responseBody, err := ioutil.ReadAll(resp.Body)
+				responseBody, err := io.ReadAll(resp.Body)
 				if err != nil {
 					t.Errorf("Could read device code response %v", err)
 				}
@@ -1615,7 +1615,7 @@ func TestOAuth2DeviceFlow(t *testing.T) {
 					t.Errorf("Error Posting Form: %v", err)
 				}
 				defer resp.Body.Close()
-				responseBody, err = ioutil.ReadAll(resp.Body)
+				responseBody, err = io.ReadAll(resp.Body)
 				if err != nil {
 					t.Errorf("Could read verification response %v", err)
 				}
@@ -1634,7 +1634,7 @@ func TestOAuth2DeviceFlow(t *testing.T) {
 					t.Errorf("Could not request device token: %v", err)
 				}
 				defer resp.Body.Close()
-				responseBody, err = ioutil.ReadAll(resp.Body)
+				responseBody, err = io.ReadAll(resp.Body)
 				if err != nil {
 					t.Errorf("Could read device token response %v", err)
 				}
diff --git a/storage/conformance/gen_jwks.go b/storage/conformance/gen_jwks.go
index 8fb12789ceb8b7fcf38d1dee9506441f886dc184..0447e32815514750e998a8bdc7134f522bbdce52 100644
--- a/storage/conformance/gen_jwks.go
+++ b/storage/conformance/gen_jwks.go
@@ -12,8 +12,8 @@ import (
 	"encoding/json"
 	"go/format"
 	"io"
-	"io/ioutil"
 	"log"
+	"os"
 	"text/template"
 
 	jose "gopkg.in/square/go-jose.v2"
@@ -104,7 +104,7 @@ func main() {
 	if err != nil {
 		log.Fatalf("gofmt failed: %v", err)
 	}
-	if err := ioutil.WriteFile("jwks.go", out, 0644); err != nil {
+	if err := os.WriteFile("jwks.go", out, 0644); err != nil {
 		log.Fatal(err)
 	}
 }
diff --git a/storage/kubernetes/client.go b/storage/kubernetes/client.go
index 1769bf49f530afddaf2339f06282b457cad09dd4..a5a72afa98566a6346f2bb446a0cd488aa3d1084 100644
--- a/storage/kubernetes/client.go
+++ b/storage/kubernetes/client.go
@@ -13,7 +13,6 @@ import (
 	"hash"
 	"hash/fnv"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -132,7 +131,7 @@ func checkHTTPErr(r *http.Response, validStatusCodes ...int) error {
 		}
 	}
 
-	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 2<<15)) // 64 KiB
+	body, err := io.ReadAll(io.LimitReader(r.Body, 2<<15)) // 64 KiB
 	if err != nil {
 		return fmt.Errorf("read response body: %v", err)
 	}
@@ -156,7 +155,7 @@ func checkHTTPErr(r *http.Response, validStatusCodes ...int) error {
 // Close the response body. The initial request is drained so the connection can
 // be reused.
 func closeResp(r *http.Response) {
-	io.Copy(ioutil.Discard, r.Body)
+	io.Copy(io.Discard, r.Body)
 	r.Body.Close()
 }
 
@@ -312,7 +311,7 @@ func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, l
 		if file == "" {
 			return nil, nil
 		}
-		return ioutil.ReadFile(file)
+		return os.ReadFile(file)
 	}
 
 	if caData, err := data(cluster.CertificateAuthorityData, cluster.CertificateAuthority); err != nil {
@@ -379,7 +378,7 @@ func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, l
 }
 
 func loadKubeConfig(kubeConfigPath string) (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) {
-	data, err := ioutil.ReadFile(kubeConfigPath)
+	data, err := os.ReadFile(kubeConfigPath)
 	if err != nil {
 		err = fmt.Errorf("read %s: %v", kubeConfigPath, err)
 		return
@@ -425,7 +424,7 @@ func namespaceFromServiceAccountJWT(s string) (string, error) {
 }
 
 func namespaceFromFile(path string) (string, error) {
-	data, err := ioutil.ReadFile(path)
+	data, err := os.ReadFile(path)
 	if err != nil {
 		return "", err
 	}
@@ -481,7 +480,7 @@ func inClusterConfig() (k8sapi.Cluster, k8sapi.AuthInfo, string, error) {
 		CertificateAuthority: serviceAccountCAPath,
 	}
 
-	token, err := ioutil.ReadFile(serviceAccountTokenPath)
+	token, err := os.ReadFile(serviceAccountTokenPath)
 	if err != nil {
 		return cluster, k8sapi.AuthInfo{}, "", err
 	}
diff --git a/storage/kubernetes/client_test.go b/storage/kubernetes/client_test.go
index bfe2d0380e9aabfe91f24cb81cf755004a7ecd93..cfd04857b6ea58f302bc9c4fca14a485fb97fc4e 100644
--- a/storage/kubernetes/client_test.go
+++ b/storage/kubernetes/client_test.go
@@ -3,7 +3,6 @@ package kubernetes
 import (
 	"hash"
 	"hash/fnv"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -72,7 +71,7 @@ func TestInClusterTransport(t *testing.T) {
 	fpath := filepath.Join(os.TempDir(), "test.in_cluster")
 	defer os.RemoveAll(fpath)
 
-	err = ioutil.WriteFile(fpath, []byte("def"), 0o644)
+	err = os.WriteFile(fpath, []byte("def"), 0o644)
 	require.NoError(t, err)
 
 	tests := []struct {
@@ -136,7 +135,7 @@ func TestGetClusterConfigNamespace(t *testing.T) {
 
 	var namespaceFile string
 	{
-		tmpfile, err := ioutil.TempFile(os.TempDir(), "test-get-cluster-config-namespace")
+		tmpfile, err := os.CreateTemp(os.TempDir(), "test-get-cluster-config-namespace")
 		require.NoError(t, err)
 
 		_, err = tmpfile.Write([]byte("namespace-from-file"))
diff --git a/storage/kubernetes/transport.go b/storage/kubernetes/transport.go
index 984184c7ab9d39ff260882df67887af3aec808e2..5d39c27fba3151fbb8627c44c9a3d1b7f3188673 100644
--- a/storage/kubernetes/transport.go
+++ b/storage/kubernetes/transport.go
@@ -1,8 +1,8 @@
 package kubernetes
 
 import (
-	"io/ioutil"
 	"net/http"
+	"os"
 	"sync"
 	"time"
 
@@ -103,7 +103,7 @@ func (c *inClusterTransportHelper) UpdateToken() {
 		return
 	}
 
-	token, err := ioutil.ReadFile(c.tokenLocation)
+	token, err := os.ReadFile(c.tokenLocation)
 	if err != nil {
 		return
 	}
diff --git a/storage/sql/config.go b/storage/sql/config.go
index 97ec6cb9b58794e054b423b3922607a3966b7d24..1aedf04cae7ff84a40d7bf11fb6234a452e023a9 100644
--- a/storage/sql/config.go
+++ b/storage/sql/config.go
@@ -5,8 +5,8 @@ import (
 	"crypto/x509"
 	"database/sql"
 	"fmt"
-	"io/ioutil"
 	"net"
+	"os"
 	"regexp"
 	"strconv"
 	"strings"
@@ -320,7 +320,7 @@ func (s *MySQL) makeTLSConfig() error {
 	cfg := &tls.Config{}
 	if s.SSL.CAFile != "" {
 		rootCertPool := x509.NewCertPool()
-		pem, err := ioutil.ReadFile(s.SSL.CAFile)
+		pem, err := os.ReadFile(s.SSL.CAFile)
 		if err != nil {
 			return err
 		}