Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
k8s-ingress
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
3
Merge Requests
3
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
uplex-varnish
k8s-ingress
Commits
5be22808
Commit
5be22808
authored
Dec 31, 2018
by
Geoff Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add support for Basic and Proxy authentication.
parent
ac720f7a
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
657 additions
and
50 deletions
+657
-50
varnishcfg-crd.yaml
deploy/varnishcfg-crd.yaml
+31
-0
ref-varnish-cfg.md
docs/ref-varnish-cfg.md
+101
-0
types.go
pkg/apis/varnishingress/v1alpha1/types.go
+28
-0
zz_generated.deepcopy.go
pkg/apis/varnishingress/v1alpha1/zz_generated.deepcopy.go
+44
-0
ingress.go
pkg/controller/ingress.go
+89
-20
secret.go
pkg/controller/secret.go
+66
-25
varnishconfig.go
pkg/controller/varnishconfig.go
+3
-5
auth.tmpl
pkg/varnish/vcl/auth.tmpl
+49
-0
auth.golden
pkg/varnish/vcl/testdata/auth.golden
+79
-0
vcl.go
pkg/varnish/vcl/vcl.go
+82
-0
vcl_test.go
pkg/varnish/vcl/vcl_test.go
+85
-0
No files found.
deploy/varnishcfg-crd.yaml
View file @
5be22808
...
...
@@ -57,6 +57,37 @@ spec:
type
:
integer
minimum
:
0
maximum
:
64
auth
:
type
:
array
minItems
:
1
items
:
type
:
object
required
:
-
realm
-
secretName
properties
:
realm
:
type
:
string
minLength
:
1
secretName
:
type
:
string
minLength
:
1
type
:
enum
:
-
basic
-
proxy
type
:
string
utf8
:
type
:
boolean
condition
:
type
:
object
properties
:
url-match
:
type
:
string
minLength
:
1
host-match
:
type
:
string
minLength
:
1
status
:
acceptedNames
:
kind
:
VarnishConfig
...
...
docs/ref-varnish-cfg.md
View file @
5be22808
...
...
@@ -156,3 +156,104 @@ spec:
window: 4
threshold: 3
```
### ``spec.auth``
The
``auth``
object is optional, and if present it contains a
non-empty array of specifications for authentication protocols (Basic
or Proxy) to be implemented by Varnish Services listed in the
``services``
array. See
[
RFC7235
](
https://tools.ietf.org/html/rfc7235
)
for the HTTP Authentication standard.
For each element of
``auth``
, these two fields are required:
*
``realm``
: string identifying the realm or "protection space" for
authentication
*
``secretName``
: the name of a Secret in the same namespace as the
VarnishConfig resource and Varnish Services that contains the
username/password credentials for authentication
The key-value pairs in the Secret are the username-password pairs to
be used for authentication.
These fields in the elements of
``auth``
are optional:
*
``type``
(string): one of the values
``basic``
or
``proxy``
to
specify the authentication protocol,
``basic``
by default
*
``utf8``
(boolean): if
``true``
, then the
``charset="UTF-8"``
field is added to the
``*-Authenticate``
response header
(
``WWW-Authentcate``
or
``Proxy-Authenticate``
) in the case of
authentication failures, to advise clients that UTF-8 character
encoding is used for the username/password (see
[
RFC 7617 2.1
](
https://tools.ietf.org/html/rfc7617#section-2.1
)
).
By default,
``charset``
is
``false``
.
*
``condition``
: conditions under which the authentication protocol is
to be executed.
If the
``condition``
object is present, it may have either or both of
these fields:
*
``url-match``
(regular expression): pattern to match against the
URL path of the request
*
``host-match``
(regular expression): pattern to match against the
``Host``
request header
If either or both of these two fields are present, then the
authentication protocol is executed for matching requests. If the
``condition``
is left out, then the authentication is required for
every client request. The patterns in
``url-match``
and
``host-match``
are implemented as
[
VCL regular expressions
](
https://varnish-cache.org/docs/6.1/reference/vcl.html#regular-expressions
)
,
and hence have the syntax and semantics of
[
PCRE
](
https://www.pcre.org/original/doc/html/
)
.
Validation for
``VarnishConfig``
reports errors at apply time if:
*
the
``auth``
array is empty
*
either of the fields
``realm``
or
``secretName``
is left out
*
any of the string fields are empty
*
``type``
has an illegal value (neither of
``basic``
or
``proxy``
)
Other errors, in particular illegal regex syntax for
``url-match``
or
``host-match``
, are not reported until VCL load time. Check the
controller log and Events generated for the Varnish Service for error
messages from the VCL compiler.
Examples:
```
spec:
# Require Basic Authentication for both the coffee and tea Services.
auth:
# For the coffee Service, require authentication for the realm
# "coffee" when the Host is "cafe.example.com" and the URL path
# begins with "/coffee". Username/password pairs are taken from
# the Secret "coffee-creds" in the same namespace, and clients
# are advised that they are encoded with UTF-8.
- realm: coffee
secretName: coffee-creds
type: basic
utf8: true
condition:
host-match: ^cafe\.example\.com$
url-match: ^/coffee($|/)
# For the tea Service, require authentication for the realm "tea"
# when the Host is "cafe.example.com" and the URL path begins with
# "/tea", with usernames/passwords from the Secret
# "tea-creds". Note that the "type" defaults to basic and can be
# left out.
- realm: tea
secretName: tea-creds
condition:
host-match: ^cafe\.example\.com$
url-match: ^/tea($|/)
```
```
spec:
# Require Proxy Authentication for the realm "ingress" for every
# request, using usernames/passwords from the Secret "proxy-creds".
auth:
- realm: ingress
secretName: proxy-creds
type: proxy
```
pkg/apis/varnishingress/v1alpha1/types.go
View file @
5be22808
...
...
@@ -51,6 +51,7 @@ type VarnishConfig struct {
type
VarnishConfigSpec
struct
{
Services
[]
string
`json:"services,omitempty"`
SelfSharding
*
SelfShardSpec
`json:"self-sharding,omitempty"`
Auth
[]
AuthSpec
`json:"auth,omitempty"`
}
// SelfShardSpec specifies self-sharding in a Varnish cluster.
...
...
@@ -70,6 +71,33 @@ type ProbeSpec struct {
Threshold
*
int32
`json:"threshold,omitempty"`
}
// AuthSpec specifies authentication (basic or proxy).
type
AuthSpec
struct
{
Realm
string
`json:"realm"`
SecretName
string
`json:"secretName"`
Type
AuthType
`json:"type,omitempty"`
Condition
*
AuthCondition
`json:"condition,omitempty"`
UTF8
bool
`json:"utf8,omitempty"`
}
// AuthType classifies the protocol for an AuthSpec.
type
AuthType
string
const
(
// Basic Authentication
Basic
AuthType
=
"basic"
// Proxy Authentication
Proxy
=
"proxy"
)
// AuthCondition specifies a condition under which an authentication
// protocol must be executed -- the URL path or the Host must match a
// pattern (or both).
type
AuthCondition
struct
{
URLRegex
string
`json:"url-match,omitempty"`
HostRegex
string
`json:"host-match,omitempty"`
}
// VarnishConfigStatus is the status for a VarnishConfig resource
// type VarnishConfigStatus struct {
// AvailableReplicas int32 `json:"availableReplicas"`
...
...
pkg/apis/varnishingress/v1alpha1/zz_generated.deepcopy.go
View file @
5be22808
...
...
@@ -34,6 +34,43 @@ import (
runtime
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func
(
in
*
AuthCondition
)
DeepCopyInto
(
out
*
AuthCondition
)
{
*
out
=
*
in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthCondition.
func
(
in
*
AuthCondition
)
DeepCopy
()
*
AuthCondition
{
if
in
==
nil
{
return
nil
}
out
:=
new
(
AuthCondition
)
in
.
DeepCopyInto
(
out
)
return
out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func
(
in
*
AuthSpec
)
DeepCopyInto
(
out
*
AuthSpec
)
{
*
out
=
*
in
if
in
.
Condition
!=
nil
{
in
,
out
:=
&
in
.
Condition
,
&
out
.
Condition
*
out
=
new
(
AuthCondition
)
**
out
=
**
in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthSpec.
func
(
in
*
AuthSpec
)
DeepCopy
()
*
AuthSpec
{
if
in
==
nil
{
return
nil
}
out
:=
new
(
AuthSpec
)
in
.
DeepCopyInto
(
out
)
return
out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func
(
in
*
ProbeSpec
)
DeepCopyInto
(
out
*
ProbeSpec
)
{
*
out
=
*
in
...
...
@@ -155,6 +192,13 @@ func (in *VarnishConfigSpec) DeepCopyInto(out *VarnishConfigSpec) {
*
out
=
new
(
SelfShardSpec
)
(
*
in
)
.
DeepCopyInto
(
*
out
)
}
if
in
.
Auth
!=
nil
{
in
,
out
:=
&
in
.
Auth
,
&
out
.
Auth
*
out
=
make
([]
AuthSpec
,
len
(
*
in
))
for
i
:=
range
*
in
{
(
*
in
)[
i
]
.
DeepCopyInto
(
&
(
*
out
)[
i
])
}
}
return
}
...
...
pkg/controller/ingress.go
View file @
5be22808
...
...
@@ -31,6 +31,7 @@ package controller
// Methods for syncing Ingresses
import
(
"encoding/base64"
"fmt"
"strconv"
...
...
@@ -161,26 +162,9 @@ func (worker *NamespaceWorker) ing2VCLSpec(
}
func
(
worker
*
NamespaceWorker
)
configSharding
(
spec
*
vcl
.
Spec
,
svc
*
api_v1
.
Service
)
error
{
vcfg
*
vcr_v1alpha1
.
VarnishConfig
,
svc
*
api_v1
.
Service
)
error
{
var
vcfg
*
vcr_v1alpha1
.
VarnishConfig
vcfgs
,
err
:=
worker
.
vcfg
.
List
(
labels
.
Everything
())
if
err
!=
nil
{
return
err
}
worker
.
log
.
Debugf
(
"Listing VarnishConfigs in namespace %s"
,
worker
.
namespace
)
for
_
,
v
:=
range
vcfgs
{
worker
.
log
.
Debugf
(
"VarnishConfig: %s/%s: %+v"
,
v
.
Namespace
,
v
.
Name
,
v
)
for
_
,
svcName
:=
range
v
.
Spec
.
Services
{
if
svcName
==
svc
.
Name
{
vcfg
=
v
break
}
}
}
if
vcfg
==
nil
||
vcfg
.
Spec
.
SelfSharding
==
nil
{
if
vcfg
.
Spec
.
SelfSharding
==
nil
{
worker
.
log
.
Debugf
(
"No cluster shard configuration for Service "
+
"%s/%s"
,
svc
.
Namespace
,
svc
.
Name
)
return
nil
...
...
@@ -268,6 +252,63 @@ func (worker *NamespaceWorker) configSharding(spec *vcl.Spec,
return
nil
}
func
(
worker
*
NamespaceWorker
)
configAuth
(
spec
*
vcl
.
Spec
,
vcfg
*
vcr_v1alpha1
.
VarnishConfig
)
error
{
if
len
(
vcfg
.
Spec
.
Auth
)
==
0
{
worker
.
log
.
Infof
(
"No Auth spec found for VarnishConfig %s/%s"
,
vcfg
.
Namespace
,
vcfg
.
Name
)
return
nil
}
worker
.
log
.
Debugf
(
"VarnishConfig %s/%s: configure %d VCL auths"
,
vcfg
.
Namespace
,
vcfg
.
Name
,
len
(
vcfg
.
Spec
.
Auth
))
spec
.
Auths
=
make
([]
vcl
.
Auth
,
0
,
len
(
vcfg
.
Spec
.
Auth
))
for
_
,
auth
:=
range
vcfg
.
Spec
.
Auth
{
worker
.
log
.
Debugf
(
"VarnishConfig %s/%s configuring VCL auth "
+
"from: %+v"
,
vcfg
.
Namespace
,
vcfg
.
Name
,
auth
)
secret
,
err
:=
worker
.
secr
.
Get
(
auth
.
SecretName
)
if
err
!=
nil
{
return
err
}
if
len
(
secret
.
Data
)
==
0
{
worker
.
log
.
Warnf
(
"No secrets found in Secret %s/%s "
+
"for realm %s in VarnishConfig %s/%s, ignoring"
,
secret
.
Namespace
,
secret
.
Name
,
auth
.
Realm
,
vcfg
.
Namespace
,
vcfg
.
Name
)
continue
}
worker
.
log
.
Debugf
(
"VarnishConfig %s/%s configure %d "
+
"credentials for realm %s"
,
vcfg
.
Namespace
,
vcfg
.
Name
,
len
(
secret
.
Data
),
auth
.
Realm
)
vclAuth
:=
vcl
.
Auth
{
Realm
:
auth
.
Realm
,
Credentials
:
make
([]
string
,
0
,
len
(
secret
.
Data
)),
UTF8
:
auth
.
UTF8
,
}
if
auth
.
Type
==
""
||
auth
.
Type
==
vcr_v1alpha1
.
Basic
{
vclAuth
.
Status
=
vcl
.
Basic
}
else
{
vclAuth
.
Status
=
vcl
.
Proxy
}
for
user
,
pass
:=
range
secret
.
Data
{
str
:=
user
+
":"
+
string
(
pass
)
cred
:=
base64
.
StdEncoding
.
EncodeToString
([]
byte
(
str
))
worker
.
log
.
Debugf
(
"VarnishConfig %s/%s: add cred %s "
+
"for realm %s to VCL config"
,
vcfg
.
Namespace
,
vcfg
.
Name
,
cred
,
vclAuth
.
Realm
)
vclAuth
.
Credentials
=
append
(
vclAuth
.
Credentials
,
cred
)
}
if
auth
.
Condition
!=
nil
{
vclAuth
.
Condition
.
URLRegex
=
auth
.
Condition
.
URLRegex
vclAuth
.
Condition
.
HostRegex
=
auth
.
Condition
.
HostRegex
}
worker
.
log
.
Debugf
(
"VarnishConfig %s/%s add VCL auth config: "
+
"%+v"
,
vcfg
.
Namespace
,
vcfg
.
Name
,
vclAuth
)
spec
.
Auths
=
append
(
spec
.
Auths
,
vclAuth
)
}
return
nil
}
func
(
worker
*
NamespaceWorker
)
hasIngress
(
svc
*
api_v1
.
Service
,
ing
*
extensions
.
Ingress
,
spec
vcl
.
Spec
)
bool
{
...
...
@@ -299,9 +340,37 @@ func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error {
return
err
}
if
err
=
worker
.
configSharding
(
&
vclSpec
,
svc
);
err
!=
nil
{
var
vcfg
*
vcr_v1alpha1
.
VarnishConfig
worker
.
log
.
Debugf
(
"Listing VarnishConfigs in namespace %s"
,
worker
.
namespace
)
vcfgs
,
err
:=
worker
.
vcfg
.
List
(
labels
.
Everything
())
if
err
!=
nil
{
return
err
}
for
_
,
v
:=
range
vcfgs
{
worker
.
log
.
Debugf
(
"VarnishConfig: %s/%s: %+v"
,
v
.
Namespace
,
v
.
Name
,
v
)
for
_
,
svcName
:=
range
v
.
Spec
.
Services
{
if
svcName
==
svc
.
Name
{
vcfg
=
v
break
}
}
}
if
vcfg
!=
nil
{
worker
.
log
.
Infof
(
"Found VarnishConfig %s/%s for Varnish "
+
"Service %s/%s"
,
vcfg
.
Namespace
,
vcfg
.
Name
,
svc
.
Namespace
,
svc
.
Name
)
if
err
=
worker
.
configSharding
(
&
vclSpec
,
vcfg
,
svc
);
err
!=
nil
{
return
err
}
if
err
=
worker
.
configAuth
(
&
vclSpec
,
vcfg
);
err
!=
nil
{
return
err
}
}
else
{
worker
.
log
.
Infof
(
"Found no VarnishConfigs for Varnish Service "
+
"%s/%s"
,
svc
.
Namespace
,
svc
.
Name
)
}
worker
.
log
.
Debugf
(
"Check if Ingress is loaded: key=%s uuid=%s hash=%0x"
,
ingKey
,
string
(
ing
.
UID
),
vclSpec
.
Canonical
()
.
DeepHash
())
...
...
pkg/controller/secret.go
View file @
5be22808
...
...
@@ -31,7 +31,10 @@ package controller
import
(
"fmt"
vcr_v1alpha1
"code.uplex.de/uplex-varnish/k8s-ingress/pkg/apis/varnishingress/v1alpha1"
api_v1
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
)
// XXX make this configurable
...
...
@@ -73,6 +76,47 @@ func (worker *NamespaceWorker) getVarnishSvcsForSecret(
return
secrSvcs
,
nil
}
func
(
worker
*
NamespaceWorker
)
updateVcfgsForSecret
(
secrName
string
)
error
{
var
vcfgs
[]
*
vcr_v1alpha1
.
VarnishConfig
vs
,
err
:=
worker
.
vcfg
.
List
(
labels
.
Everything
())
if
err
!=
nil
{
return
err
}
for
_
,
v
:=
range
vs
{
for
_
,
auth
:=
range
v
.
Spec
.
Auth
{
if
auth
.
SecretName
==
secrName
{
vcfgs
=
append
(
vcfgs
,
v
)
}
}
}
if
len
(
vcfgs
)
==
0
{
worker
.
log
.
Infof
(
"No VarnishConfigs found for secret: "
+
"%s/%s"
,
worker
.
namespace
,
secrName
)
return
nil
}
for
_
,
vcfg
:=
range
vcfgs
{
worker
.
log
.
Infof
(
"Requeuing VarnishConfig %s/%s "
+
"after update for secret %s/%s"
,
vcfg
.
Namespace
,
vcfg
.
Name
,
worker
.
namespace
,
secrName
)
worker
.
queue
.
Add
(
vcfg
)
}
return
nil
}
func
(
worker
*
NamespaceWorker
)
updateVarnishSvcsForSecret
(
svcs
[]
*
api_v1
.
Service
,
secretKey
string
)
error
{
for
_
,
svc
:=
range
svcs
{
svcKey
:=
svc
.
Namespace
+
"/"
+
svc
.
Name
if
err
:=
worker
.
vController
.
UpdateSvcForSecret
(
svcKey
,
secretKey
);
err
!=
nil
{
return
err
}
}
return
nil
}
func
(
worker
*
NamespaceWorker
)
syncSecret
(
key
string
)
error
{
worker
.
log
.
Infof
(
"Syncing Secret: %s/%s"
,
worker
.
namespace
,
key
)
secret
,
err
:=
worker
.
secr
.
Get
(
key
)
...
...
@@ -82,18 +126,10 @@ func (worker *NamespaceWorker) syncSecret(key string) error {
app
,
ok
:=
secret
.
Labels
[
labelKey
]
if
!
ok
||
app
!=
labelVal
{
worker
.
log
.
Infof
(
"Not a Varnish
admin secret, ignoring
: %s/%s"
,
worker
.
log
.
Infof
(
"Not a Varnish
secret
: %s/%s"
,
secret
.
Namespace
,
secret
.
Name
)
return
nil
}
secretData
,
exists
:=
secret
.
Data
[
admSecretKey
]
if
!
exists
{
return
fmt
.
Errorf
(
"Secret %s/%s does not have key %s"
,
secret
.
Namespace
,
secret
.
Name
,
admSecretKey
)
}
secretKey
:=
secret
.
Namespace
+
"/"
+
secret
.
Name
worker
.
log
.
Debugf
(
"Setting secret %s"
,
secretKey
)
worker
.
vController
.
SetAdmSecret
(
secretKey
,
secretData
)
svcs
,
err
:=
worker
.
getVarnishSvcsForSecret
(
secret
.
Name
)
if
err
!=
nil
{
...
...
@@ -101,35 +137,40 @@ func (worker *NamespaceWorker) syncSecret(key string) error {
}
worker
.
log
.
Debugf
(
"Found Varnish services for secret %s/%s: %v"
,
secret
.
Namespace
,
secret
.
Name
,
svcs
)
for
_
,
svc
:=
range
svcs
{
svcKey
:=
svc
.
Namespace
+
"/"
+
svc
.
Name
if
err
=
worker
.
vController
.
UpdateSvcForSecret
(
svcKey
,
secretKey
);
err
!=
nil
{
return
err
if
len
(
svcs
)
==
0
{
worker
.
log
.
Infof
(
"No Varnish services with admin secret: %s/%s"
,
secret
.
Namespace
,
secret
.
Name
)
return
worker
.
updateVcfgsForSecret
(
secret
.
Name
)
}
secretData
,
exists
:=
secret
.
Data
[
admSecretKey
]
if
!
exists
{
return
fmt
.
Errorf
(
"Secret %s/%s does not have key %s"
,
secret
.
Namespace
,
secret
.
Name
,
admSecretKey
)
}
return
nil
secretKey
:=
secret
.
Namespace
+
"/"
+
secret
.
Name
worker
.
log
.
Debugf
(
"Setting secret %s"
,
secretKey
)
worker
.
vController
.
SetAdmSecret
(
secretKey
,
secretData
)
return
worker
.
updateVarnishSvcsForSecret
(
svcs
,
secretKey
)
}
func
(
worker
*
NamespaceWorker
)
deleteSecret
(
key
string
)
error
{
worker
.
log
.
Infof
(
"Deleting Secret: %s
"
,
key
)
worker
.
log
.
Infof
(
"Deleting Secret: %s
/%s"
,
worker
.
namespace
,
key
)
svcs
,
err
:=
worker
.
getVarnishSvcsForSecret
(
key
)
if
err
!=
nil
{
return
err
}
worker
.
log
.
Debugf
(
"Found Varnish services for secret %s/%s: %v"
,
worker
.
namespace
,
key
,
svcs
)
if
len
(
svcs
)
==
0
{
worker
.
log
.
Infof
(
"No Varnish services with admin secret: %s/%s"
,
worker
.
namespace
,
key
)
return
worker
.
updateVcfgsForSecret
(
key
)
}
secretKey
:=
worker
.
namespace
+
"/"
+
key
worker
.
vController
.
DeleteAdmSecret
(
secretKey
)
for
_
,
svc
:=
range
svcs
{
svcKey
:=
svc
.
Namespace
+
"/"
+
svc
.
Name
if
err
=
worker
.
vController
.
UpdateSvcForSecret
(
svcKey
,
secretKey
);
err
!=
nil
{
return
err
}
}
return
nil
return
worker
.
updateVarnishSvcsForSecret
(
svcs
,
secretKey
)
}
pkg/controller/varnishconfig.go
View file @
5be22808
...
...
@@ -39,6 +39,9 @@ import (
// the Timeout, Interval and Initial fields, and that Window and
// Threshold have been checked for permitted ranges.
func
validateSharding
(
spec
*
vcr_v1alpha1
.
SelfShardSpec
)
error
{
if
spec
==
nil
{
return
nil
}
if
spec
.
Probe
.
Window
!=
nil
&&
spec
.
Probe
.
Threshold
!=
nil
&&
*
spec
.
Probe
.
Threshold
>
*
spec
.
Probe
.
Window
{
return
fmt
.
Errorf
(
"Threshold (%d) may not be greater than "
+
...
...
@@ -62,11 +65,6 @@ func (worker *NamespaceWorker) syncVcfg(key string) error {
"ignoring"
,
vcfg
.
Namespace
,
vcfg
.
Name
)
return
nil
}
if
vcfg
.
Spec
.
SelfSharding
==
nil
{
worker
.
log
.
Infof
(
"VarnishConfig %s/%s: no config defined, "
+
"ignoring"
,
vcfg
.
Namespace
,
vcfg
.
Name
)
return
nil
}
if
err
=
validateSharding
(
vcfg
.
Spec
.
SelfSharding
);
err
!=
nil
{
return
fmt
.
Errorf
(
"VarnishConfig %s/%s invalid sharding "
+
...
...
pkg/varnish/vcl/auth.tmpl
0 → 100644
View file @
5be22808
import re2;
sub vcl_init {
{{- range $auth := .Auths}}
new vk8s_{{vclMangle .Realm}}_auth = re2.set(anchor=both);
{{- range $cred := .Credentials}}
vk8s_{{vclMangle $auth.Realm}}_auth.add("\s*Basic\s+\Q{{$cred}}\E\s*");
{{- end}}
vk8s_{{vclMangle .Realm}}_auth.compile();
{{end -}}
}
sub vcl_recv {
{{- range .Auths}}
if (
{{- if ne .Condition.HostRegex ""}}
req.http.Host ~ "{{.Condition.HostRegex}}" &&
{{- end}}
{{- if ne .Condition.URLRegex ""}}
req.url ~ "{{.Condition.URLRegex}}" &&
{{- end}}
{{- if eq .Status 401}}
!vk8s_{{vclMangle .Realm}}_auth.match(req.http.Authorization)
{{- else}}
!vk8s_{{vclMangle .Realm}}_auth.match(req.http.Proxy-Authorization)
{{- end}}
) {
{{- if .UTF8 }}
set req.http.VK8S-Authenticate =
{"Basic realm="{{.Realm}}", charset="UTF-8""};
{{- else}}
set req.http.VK8S-Authenticate = {"Basic realm="{{.Realm}}""};
{{- end}}
return(synth(60000 + {{.Status}}));
}
{{- end}}
}
sub vcl_synth {
if (resp.status == 60401) {
set resp.http.WWW-Authenticate = req.http.VK8S-Authenticate;
return(deliver);
}
if (resp.status == 60407) {
set resp.http.Proxy-Authenticate = req.http.VK8S-Authenticate;
return(deliver);
}
}
pkg/varnish/vcl/testdata/auth.golden
0 → 100644
View file @
5be22808
import re2;
sub vcl_init {
new vk8s_foo_auth = re2.set(anchor=both);
vk8s_foo_auth.add("\s*Basic\s+\QQWxhZGRpbjpvcGVuIHNlc2FtZQ==\E\s*");
vk8s_foo_auth.add("\s*Basic\s+\QQWxhZGRpbjpPcGVuU2VzYW1l\E\s*");
vk8s_foo_auth.compile();
new vk8s_bar_auth = re2.set(anchor=both);
vk8s_bar_auth.add("\s*Basic\s+\QZm9vOmJhcg==\E\s*");
vk8s_bar_auth.add("\s*Basic\s+\QYmF6OnF1dXg=\E\s*");
vk8s_bar_auth.compile();
new vk8s_baz_auth = re2.set(anchor=both);
vk8s_baz_auth.add("\s*Basic\s+\QdXNlcjpwYXNzd29yZDE=\E\s*");
vk8s_baz_auth.add("\s*Basic\s+\QbmFtZTpzZWNyZXQ=\E\s*");
vk8s_baz_auth.compile();
new vk8s_quux_auth = re2.set(anchor=both);
vk8s_quux_auth.add("\s*Basic\s+\QYmVudXR6ZXI6Z2VoZWlt\E\s*");
vk8s_quux_auth.add("\s*Basic\s+\QQWxiZXJ0IEFkZGluOm9wZW4gc2V6IG1l\E\s*");
vk8s_quux_auth.compile();
new vk8s_urlhost_auth = re2.set(anchor=both);
vk8s_urlhost_auth.add("\s*Basic\s+\QdXJsOmhvc3Q=\E\s*");
vk8s_urlhost_auth.add("\s*Basic\s+\QYWRtaW46c3VwZXJwb3dlcnM=\E\s*");
vk8s_urlhost_auth.compile();
}
sub vcl_recv {
if (
!vk8s_foo_auth.match(req.http.Authorization)
) {
set req.http.VK8S-Authenticate = {"Basic realm="foo""};
return(synth(60000 + 401));
}
if (
!vk8s_bar_auth.match(req.http.Proxy-Authorization)
) {
set req.http.VK8S-Authenticate = {"Basic realm="bar""};
return(synth(60000 + 407));
}
if (
req.http.Host ~ "^baz\.com$" &&
!vk8s_baz_auth.match(req.http.Authorization)
) {
set req.http.VK8S-Authenticate =
{"Basic realm="baz", charset="UTF-8""};
return(synth(60000 + 401));
}
if (
req.url ~ "^/baz/quux" &&
!vk8s_quux_auth.match(req.http.Proxy-Authorization)
) {
set req.http.VK8S-Authenticate =
{"Basic realm="quux", charset="UTF-8""};
return(synth(60000 + 407));
}
if (
req.http.Host ~ "^url\.regex\.org$" &&
req.url ~ "^/secret/path" &&
!vk8s_urlhost_auth.match(req.http.Authorization)
) {
set req.http.VK8S-Authenticate = {"Basic realm="urlhost""};
return(synth(60000 + 401));
}
}
sub vcl_synth {
if (resp.status == 60401) {
set resp.http.WWW-Authenticate = req.http.VK8S-Authenticate;
return(deliver);
}
if (resp.status == 60407) {
set resp.http.Proxy-Authenticate = req.http.VK8S-Authenticate;
return(deliver);
}
}
pkg/varnish/vcl/vcl.go
View file @
5be22808
...
...
@@ -157,6 +157,62 @@ func (shard ShardCluster) hash(hash hash.Hash) {
hash
.
Write
([]
byte
(
shard
.
MaxSecondaryTTL
))
}
// Condition specifies conditions under which an authentication
// protocols must be executed -- the URL path or the Host must match
// patterns, the request must be received from a TLS offloader, or any
// combination of the three.
type
Condition
struct
{
URLRegex
string
HostRegex
string
TLS
bool
}
// AuthStatus is the response code to be sent for authentication
// failures, and serves to distinguish the protocols.
type
AuthStatus
uint16
const
(
// Basic Authentication
Basic
AuthStatus
=
401
// Proxy Authentication
Proxy
=
407
)
// Auth specifies Basic or Proxy Authentication, derived from an
// AuthSpec in a VarnishConfig resource.
type
Auth
struct
{
Realm
string
Credentials
[]
string
Status
AuthStatus
Condition
Condition
UTF8
bool
}
func
(
auth
Auth
)
hash
(
hash
hash
.
Hash
)
{
hash
.
Write
([]
byte
(
auth
.
Realm
))
for
_
,
cred
:=
range
auth
.
Credentials
{
hash
.
Write
([]
byte
(
cred
))
}
statusBytes
:=
make
([]
byte
,
2
)
binary
.
BigEndian
.
PutUint16
(
statusBytes
,
uint16
(
auth
.
Status
))
hash
.
Write
(
statusBytes
)
hash
.
Write
([]
byte
(
auth
.
Condition
.
URLRegex
))
hash
.
Write
([]
byte
(
auth
.
Condition
.
HostRegex
))
if
auth
.
Condition
.
TLS
{
hash
.
Write
([]
byte
(
"TLS"
))
}
if
auth
.
UTF8
{
hash
.
Write
([]
byte
(
"UTF8"
))
}
}
// interface for sorting []Auth
type
byRealm
[]
Auth
func
(
a
byRealm
)
Len
()
int
{
return
len
(
a
)
}
func
(
a
byRealm
)
Swap
(
i
,
j
int
)
{
a
[
i
],
a
[
j
]
=
a
[
j
],
a
[
i
]
}
func
(
a
byRealm
)
Less
(
i
,
j
int
)
bool
{
return
a
[
i
]
.
Realm
<
a
[
j
]
.
Realm
}
// Spec is the specification for a VCL configuration derived from
// Ingresses and VarnishConfig Custom Resources. This abstracts the
// VCL to be loaded by all instances of a Varnish Service.
...
...
@@ -174,6 +230,10 @@ type Spec struct {
// ShardCluster is derived from the self-sharding
// specification in a VarnishConfig resource.
ShardCluster
ShardCluster
// Auths is a list of specifications for Basic or Proxy
// Authentication, derived from the Auth section of a
// VarnishConfig.
Auths
[]
Auth
}
// DeepHash computes a 64-bit hash value from a Spec such that if two
...
...
@@ -196,6 +256,9 @@ func (spec Spec) DeepHash() uint64 {
spec
.
AllServices
[
svc
]
.
hash
(
hash
)
}
spec
.
ShardCluster
.
hash
(
hash
)
for
_
,
auth
:=
range
spec
.
Auths
{
auth
.
hash
(
hash
)
}
return
hash
.
Sum64
()
}
...
...
@@ -209,6 +272,7 @@ func (spec Spec) Canonical() Spec {
Rules
:
make
([]
Rule
,
len
(
spec
.
Rules
)),
AllServices
:
make
(
map
[
string
]
Service
,
len
(
spec
.
AllServices
)),
ShardCluster
:
spec
.
ShardCluster
,
Auths
:
make
([]
Auth
,
len
(
spec
.
Auths
)),
}
copy
(
canon
.
DefaultService
.
Addresses
,
spec
.
DefaultService
.
Addresses
)
sort
.
Stable
(
byIPPort
(
canon
.
DefaultService
.
Addresses
))
...
...
@@ -227,6 +291,11 @@ func (spec Spec) Canonical() Spec {
for
_
,
node
:=
range
canon
.
ShardCluster
.
Nodes
{
sort
.
Stable
(
byIPPort
(
node
.
Addresses
))
}
copy
(
canon
.
Auths
,
spec
.
Auths
)
sort
.
Stable
(
byRealm
(
canon
.
Auths
))
for
_
,
auth
:=
range
canon
.
Auths
{
sort
.
Strings
(
auth
.
Credentials
)
}
return
canon
}
...
...
@@ -247,11 +316,13 @@ var fMap = template.FuncMap{
const
(
ingTmplSrc
=
"vcl.tmpl"
shardTmplSrc
=
"self-shard.tmpl"
authTmplSrc
=
"auth.tmpl"
)
var
(
ingressTmpl
*
template
.
Template
shardTmpl
*
template
.
Template
authTmpl
*
template
.
Template
symPattern
=
regexp
.
MustCompile
(
"^[[:alpha:]][[:word:]-]*$"
)
first
=
regexp
.
MustCompile
(
"[[:alpha:]]"
)
restIllegal
=
regexp
.
MustCompile
(
"[^[:word:]-]+"
)
...
...
@@ -262,6 +333,7 @@ func InitTemplates(tmplDir string) error {
var
err
error
ingTmplPath
:=
path
.
Join
(
tmplDir
,
ingTmplSrc
)
shardTmplPath
:=
path
.
Join
(
tmplDir
,
shardTmplSrc
)
authTmplPath
:=
path
.
Join
(
tmplDir
,
authTmplSrc
)
ingressTmpl
,
err
=
template
.
New
(
ingTmplSrc
)
.
Funcs
(
fMap
)
.
ParseFiles
(
ingTmplPath
)
if
err
!=
nil
{
...
...
@@ -272,6 +344,11 @@ func InitTemplates(tmplDir string) error {
if
err
!=
nil
{
return
err
}
authTmpl
,
err
=
template
.
New
(
authTmplSrc
)
.
Funcs
(
fMap
)
.
ParseFiles
(
authTmplPath
)
if
err
!=
nil
{
return
err
}
return
nil
}
...
...
@@ -295,6 +372,11 @@ func (spec Spec) GetSrc() (string, error) {
return
""
,
err
}
}
if
len
(
spec
.
Auths
)
>
0
{
if
err
:=
authTmpl
.
Execute
(
&
buf
,
spec
);
err
!=
nil
{
return
""
,
err
}
}
return
buf
.
String
(),
nil
}
...
...
pkg/varnish/vcl/vcl_test.go
View file @
5be22808
...
...
@@ -312,3 +312,88 @@ func TestGetSrc(t *testing.T) {
}
}
}
var
auths
=
Spec
{
Auths
:
[]
Auth
{
{
Realm
:
"foo"
,
Status
:
Basic
,
Credentials
:
[]
string
{
"QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
,
"QWxhZGRpbjpPcGVuU2VzYW1l"
,
},
},
{
Realm
:
"bar"
,
Status
:
Proxy
,
Credentials
:
[]
string
{
"Zm9vOmJhcg=="
,
"YmF6OnF1dXg="
,
},
UTF8
:
false
,
},
{
Realm
:
"baz"
,
Status
:
Basic
,
Credentials
:
[]
string
{
"dXNlcjpwYXNzd29yZDE="
,
"bmFtZTpzZWNyZXQ="
,
},
Condition
:
Condition
{
HostRegex
:
`^baz\.com$`
,
},
UTF8
:
true
,
},
{
Realm
:
"quux"
,
Status
:
Proxy
,
Credentials
:
[]
string
{
"YmVudXR6ZXI6Z2VoZWlt"
,
"QWxiZXJ0IEFkZGluOm9wZW4gc2V6IG1l"
,
},
Condition
:
Condition
{
URLRegex
:
"^/baz/quux"
,
},
UTF8
:
true
,
},
{
Realm
:
"urlhost"
,
Status
:
Basic
,
Credentials
:
[]
string
{
"dXJsOmhvc3Q="
,
"YWRtaW46c3VwZXJwb3dlcnM="
,
},
Condition
:
Condition
{
HostRegex
:
`^url\.regex\.org$`
,
URLRegex
:
"^/secret/path"
,
},
},
},
}
func
TestAuthTemplate
(
t
*
testing
.
T
)
{
var
buf
bytes
.
Buffer
gold
:=
"auth.golden"
tmplName
:=
"auth.tmpl"
tmpl
,
err
:=
template
.
New
(
tmplName
)
.
Funcs
(
fMap
)
.
ParseFiles
(
tmplName
)
if
err
!=
nil
{
t
.
Error
(
"Cannot parse auth template:"
,
err
)
return
}
if
err
:=
tmpl
.
Execute
(
&
buf
,
auths
);
err
!=
nil
{
t
.
Error
(
"auths template Execute():"
,
err
)
return
}
ok
,
err
:=
cmpGold
(
buf
.
Bytes
(),
gold
)
if
err
!=
nil
{
t
.
Fatalf
(
"Reading %s: %v"
,
gold
,
err
)
}
if
!
ok
{
t
.
Errorf
(
"Generated VCL for authorization does not match gold "
+
"file: %s"
,
gold
)
if
testing
.
Verbose
()
{
t
.
Logf
(
"Generated: %s"
,
buf
.
String
())
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment