Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
68f69b2c
Commit
68f69b2c
authored
Mar 06, 2017
by
xilabao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow multiple providers for authorizationMode
parent
df70b30e
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
87 additions
and
64 deletions
+87
-64
fuzzer.go
cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go
+1
-1
types.go
cmd/kubeadm/app/apis/kubeadm/types.go
+6
-6
defaults.go
cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go
+2
-2
types.go
cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go
+6
-6
validation.go
cmd/kubeadm/app/apis/kubeadm/validation/validation.go
+17
-5
validation_test.go
cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go
+14
-11
defaults.go
cmd/kubeadm/app/cmd/defaults.go
+1
-1
manifests.go
cmd/kubeadm/app/master/manifests.go
+12
-11
manifests_test.go
cmd/kubeadm/app/master/manifests_test.go
+21
-16
checks.go
cmd/kubeadm/app/preflight/checks.go
+7
-5
No files found.
cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go
View file @
68f69b2c
...
@@ -32,7 +32,7 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} {
...
@@ -32,7 +32,7 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} {
obj
.
API
.
AdvertiseAddress
=
"foo"
obj
.
API
.
AdvertiseAddress
=
"foo"
obj
.
Networking
.
ServiceSubnet
=
"foo"
obj
.
Networking
.
ServiceSubnet
=
"foo"
obj
.
Networking
.
DNSDomain
=
"foo"
obj
.
Networking
.
DNSDomain
=
"foo"
obj
.
AuthorizationMode
=
"foo"
obj
.
AuthorizationMode
s
=
[]
string
{
"foo"
}
obj
.
CertificatesDir
=
"foo"
obj
.
CertificatesDir
=
"foo"
obj
.
APIServerCertSANs
=
[]
string
{}
obj
.
APIServerCertSANs
=
[]
string
{}
obj
.
Token
=
"foo"
obj
.
Token
=
"foo"
...
...
cmd/kubeadm/app/apis/kubeadm/types.go
View file @
68f69b2c
...
@@ -33,12 +33,12 @@ type EnvParams struct {
...
@@ -33,12 +33,12 @@ type EnvParams struct {
type
MasterConfiguration
struct
{
type
MasterConfiguration
struct
{
metav1
.
TypeMeta
metav1
.
TypeMeta
API
API
API
API
Etcd
Etcd
Etcd
Etcd
Networking
Networking
Networking
Networking
KubernetesVersion
string
KubernetesVersion
string
CloudProvider
string
CloudProvider
string
AuthorizationMode
string
AuthorizationMode
s
[]
string
Token
string
Token
string
TokenTTL
time
.
Duration
TokenTTL
time
.
Duration
...
...
cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go
View file @
68f69b2c
...
@@ -61,8 +61,8 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
...
@@ -61,8 +61,8 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
obj
.
Networking
.
DNSDomain
=
DefaultServiceDNSDomain
obj
.
Networking
.
DNSDomain
=
DefaultServiceDNSDomain
}
}
if
obj
.
AuthorizationMode
==
""
{
if
len
(
obj
.
AuthorizationModes
)
==
0
{
obj
.
AuthorizationMode
=
DefaultAuthorizationMode
obj
.
AuthorizationMode
s
=
[]
string
{
DefaultAuthorizationMode
}
}
}
if
obj
.
CertificatesDir
==
""
{
if
obj
.
CertificatesDir
==
""
{
...
...
cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go
View file @
68f69b2c
...
@@ -25,12 +25,12 @@ import (
...
@@ -25,12 +25,12 @@ import (
type
MasterConfiguration
struct
{
type
MasterConfiguration
struct
{
metav1
.
TypeMeta
`json:",inline"`
metav1
.
TypeMeta
`json:",inline"`
API
API
`json:"api"`
API
API
`json:"api"`
Etcd
Etcd
`json:"etcd"`
Etcd
Etcd
`json:"etcd"`
Networking
Networking
`json:"networking"`
Networking
Networking
`json:"networking"`
KubernetesVersion
string
`json:"kubernetesVersion"`
KubernetesVersion
string
`json:"kubernetesVersion"`
CloudProvider
string
`json:"cloudProvider"`
CloudProvider
string
`json:"cloudProvider"`
AuthorizationMode
string
`json:"authorizationMode
"`
AuthorizationMode
s
[]
string
`json:"authorizationModes
"`
Token
string
`json:"token"`
Token
string
`json:"token"`
TokenTTL
time
.
Duration
`json:"tokenTTL"`
TokenTTL
time
.
Duration
`json:"tokenTTL"`
...
...
cmd/kubeadm/app/apis/kubeadm/validation/validation.go
View file @
68f69b2c
...
@@ -52,7 +52,7 @@ var cloudproviders = []string{
...
@@ -52,7 +52,7 @@ var cloudproviders = []string{
func
ValidateMasterConfiguration
(
c
*
kubeadm
.
MasterConfiguration
)
field
.
ErrorList
{
func
ValidateMasterConfiguration
(
c
*
kubeadm
.
MasterConfiguration
)
field
.
ErrorList
{
allErrs
:=
field
.
ErrorList
{}
allErrs
:=
field
.
ErrorList
{}
allErrs
=
append
(
allErrs
,
ValidateCloudProvider
(
c
.
CloudProvider
,
field
.
NewPath
(
"cloudprovider"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateCloudProvider
(
c
.
CloudProvider
,
field
.
NewPath
(
"cloudprovider"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateAuthorizationMode
(
c
.
AuthorizationMode
,
field
.
NewPath
(
"authorization-mode"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateAuthorizationMode
s
(
c
.
AuthorizationModes
,
field
.
NewPath
(
"authorization-mode"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateNetworking
(
&
c
.
Networking
,
field
.
NewPath
(
"networking"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateNetworking
(
&
c
.
Networking
,
field
.
NewPath
(
"networking"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateAPIServerCertSANs
(
c
.
APIServerCertSANs
,
field
.
NewPath
(
"cert-altnames"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateAPIServerCertSANs
(
c
.
APIServerCertSANs
,
field
.
NewPath
(
"cert-altnames"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateAbsolutePath
(
c
.
CertificatesDir
,
field
.
NewPath
(
"certificates-dir"
))
...
)
allErrs
=
append
(
allErrs
,
ValidateAbsolutePath
(
c
.
CertificatesDir
,
field
.
NewPath
(
"certificates-dir"
))
...
)
...
@@ -70,11 +70,23 @@ func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList {
...
@@ -70,11 +70,23 @@ func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList {
return
allErrs
return
allErrs
}
}
func
ValidateAuthorizationMode
(
authzMode
string
,
fldPath
*
field
.
Path
)
field
.
ErrorList
{
func
ValidateAuthorizationModes
(
authzModes
[]
string
,
fldPath
*
field
.
Path
)
field
.
ErrorList
{
if
!
authzmodes
.
IsValidAuthorizationMode
(
authzMode
)
{
allErrs
:=
field
.
ErrorList
{}
return
field
.
ErrorList
{
field
.
Invalid
(
fldPath
,
authzMode
,
"invalid authorization mode"
)}
for
_
,
authzMode
:=
range
authzModes
{
if
!
authzmodes
.
IsValidAuthorizationMode
(
authzMode
)
{
allErrs
=
append
(
allErrs
,
field
.
Invalid
(
fldPath
,
authzMode
,
"invalid authorization mode"
))
}
}
found
:=
map
[
string
]
bool
{}
for
_
,
authzMode
:=
range
authzModes
{
if
found
[
authzMode
]
{
allErrs
=
append
(
allErrs
,
field
.
Invalid
(
fldPath
,
authzMode
,
"duplicate authorization mode"
))
continue
}
found
[
authzMode
]
=
true
}
}
return
field
.
ErrorList
{}
return
allErrs
}
}
func
ValidateDiscovery
(
c
*
kubeadm
.
NodeConfiguration
,
fldPath
*
field
.
Path
)
field
.
ErrorList
{
func
ValidateDiscovery
(
c
*
kubeadm
.
NodeConfiguration
,
fldPath
*
field
.
Path
)
field
.
ErrorList
{
...
...
cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go
View file @
68f69b2c
...
@@ -45,23 +45,26 @@ func TestValidateTokenDiscovery(t *testing.T) {
...
@@ -45,23 +45,26 @@ func TestValidateTokenDiscovery(t *testing.T) {
}
}
}
}
func
TestValidateAuthorizationMode
(
t
*
testing
.
T
)
{
func
TestValidateAuthorizationMode
s
(
t
*
testing
.
T
)
{
var
tests
=
[]
struct
{
var
tests
=
[]
struct
{
s
string
s
[]
string
f
*
field
.
Path
f
*
field
.
Path
expected
bool
expected
bool
}{
}{
{
""
,
nil
,
false
},
{[]
string
{
""
},
nil
,
false
},
{
"rBAC"
,
nil
,
false
},
// not supported
{[]
string
{
"rBAC"
},
nil
,
false
},
// not supported
{
"not valid"
,
nil
,
false
},
// not supported
{[]
string
{
"rBAC"
,
"Webhook"
},
nil
,
false
},
// not supported
{
"RBAC"
,
nil
,
true
},
// supported
{[]
string
{
"RBAC"
,
"Webhook"
,
"Webhook"
},
nil
,
false
},
// not supported
{
"Webhook"
,
nil
,
true
},
// supported
{[]
string
{
"not valid"
},
nil
,
false
},
// not supported
{[]
string
{
"RBAC"
},
nil
,
true
},
// supported
{[]
string
{
"Webhook"
},
nil
,
true
},
// supported
{[]
string
{
"RBAC"
,
"Webhook"
},
nil
,
true
},
// supported
}
}
for
_
,
rt
:=
range
tests
{
for
_
,
rt
:=
range
tests
{
actual
:=
ValidateAuthorizationMode
(
rt
.
s
,
rt
.
f
)
actual
:=
ValidateAuthorizationMode
s
(
rt
.
s
,
rt
.
f
)
if
(
len
(
actual
)
==
0
)
!=
rt
.
expected
{
if
(
len
(
actual
)
==
0
)
!=
rt
.
expected
{
t
.
Errorf
(
t
.
Errorf
(
"failed ValidateAuthorizationMode:
\n\t
expected: %t
\n\t
actual: %t"
,
"failed ValidateAuthorizationMode
s
:
\n\t
expected: %t
\n\t
actual: %t"
,
rt
.
expected
,
rt
.
expected
,
(
len
(
actual
)
==
0
),
(
len
(
actual
)
==
0
),
)
)
...
@@ -172,7 +175,7 @@ func TestValidateMasterConfiguration(t *testing.T) {
...
@@ -172,7 +175,7 @@ func TestValidateMasterConfiguration(t *testing.T) {
}{
}{
{
&
kubeadm
.
MasterConfiguration
{},
false
},
{
&
kubeadm
.
MasterConfiguration
{},
false
},
{
&
kubeadm
.
MasterConfiguration
{
{
&
kubeadm
.
MasterConfiguration
{
AuthorizationMode
:
"RBAC"
,
AuthorizationMode
s
:
[]
string
{
"RBAC"
}
,
Networking
:
kubeadm
.
Networking
{
Networking
:
kubeadm
.
Networking
{
ServiceSubnet
:
"10.96.0.1/12"
,
ServiceSubnet
:
"10.96.0.1/12"
,
DNSDomain
:
"cluster.local"
,
DNSDomain
:
"cluster.local"
,
...
@@ -180,7 +183,7 @@ func TestValidateMasterConfiguration(t *testing.T) {
...
@@ -180,7 +183,7 @@ func TestValidateMasterConfiguration(t *testing.T) {
CertificatesDir
:
"/some/cert/dir"
,
CertificatesDir
:
"/some/cert/dir"
,
},
false
},
},
false
},
{
&
kubeadm
.
MasterConfiguration
{
{
&
kubeadm
.
MasterConfiguration
{
AuthorizationMode
:
"RBAC"
,
AuthorizationMode
s
:
[]
string
{
"RBAC"
}
,
Networking
:
kubeadm
.
Networking
{
Networking
:
kubeadm
.
Networking
{
ServiceSubnet
:
"10.96.0.1/12"
,
ServiceSubnet
:
"10.96.0.1/12"
,
DNSDomain
:
"cluster.local"
,
DNSDomain
:
"cluster.local"
,
...
...
cmd/kubeadm/app/cmd/defaults.go
View file @
68f69b2c
...
@@ -63,7 +63,7 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
...
@@ -63,7 +63,7 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
}
}
fmt
.
Printf
(
"[init] Using Kubernetes version: %s
\n
"
,
cfg
.
KubernetesVersion
)
fmt
.
Printf
(
"[init] Using Kubernetes version: %s
\n
"
,
cfg
.
KubernetesVersion
)
fmt
.
Printf
(
"[init] Using Authorization mode: %
s
\n
"
,
cfg
.
AuthorizationMode
)
fmt
.
Printf
(
"[init] Using Authorization mode: %
v
\n
"
,
cfg
.
AuthorizationModes
)
// Warn about the limitations with the current cloudprovider solution.
// Warn about the limitations with the current cloudprovider solution.
if
cfg
.
CloudProvider
!=
""
{
if
cfg
.
CloudProvider
!=
""
{
...
...
cmd/kubeadm/app/master/manifests.go
View file @
68f69b2c
...
@@ -327,7 +327,7 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
...
@@ -327,7 +327,7 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
command
=
getComponentBaseCommand
(
apiServer
)
command
=
getComponentBaseCommand
(
apiServer
)
command
=
append
(
command
,
getExtraParameters
(
cfg
.
APIServerExtraArgs
,
defaultArguments
)
...
)
command
=
append
(
command
,
getExtraParameters
(
cfg
.
APIServerExtraArgs
,
defaultArguments
)
...
)
command
=
append
(
command
,
getAuthzParameters
(
cfg
.
AuthorizationMode
)
...
)
command
=
append
(
command
,
getAuthzParameters
(
cfg
.
AuthorizationMode
s
)
...
)
if
selfHosted
{
if
selfHosted
{
command
=
append
(
command
,
"--advertise-address=$(POD_IP)"
)
command
=
append
(
command
,
"--advertise-address=$(POD_IP)"
)
...
@@ -457,22 +457,23 @@ func getSelfHostedAPIServerEnv() []api.EnvVar {
...
@@ -457,22 +457,23 @@ func getSelfHostedAPIServerEnv() []api.EnvVar {
return
append
(
getProxyEnvVars
(),
podIPEnvVar
)
return
append
(
getProxyEnvVars
(),
podIPEnvVar
)
}
}
func
getAuthzParameters
(
authzMode
string
)
[]
string
{
func
getAuthzParameters
(
modes
[]
string
)
[]
string
{
command
:=
[]
string
{}
command
:=
[]
string
{}
// RBAC is always on. If the user specified
// RBAC is always on. If the user specified
authzModes
:=
[]
string
{
authzmodes
.
ModeRBAC
}
authzModes
:=
[]
string
{
authzmodes
.
ModeRBAC
}
if
len
(
authzMode
)
!=
0
&&
authzMode
!=
authzmodes
.
ModeRBAC
{
for
_
,
authzMode
:=
range
modes
{
authzModes
=
append
(
authzModes
,
authzMode
)
if
len
(
authzMode
)
!=
0
&&
authzMode
!=
authzmodes
.
ModeRBAC
{
authzModes
=
append
(
authzModes
,
authzMode
)
}
switch
authzMode
{
case
authzmodes
.
ModeABAC
:
command
=
append
(
command
,
"--authorization-policy-file="
+
kubeadmconstants
.
AuthorizationPolicyPath
)
case
authzmodes
.
ModeWebhook
:
command
=
append
(
command
,
"--authorization-webhook-config-file="
+
kubeadmconstants
.
AuthorizationWebhookConfigPath
)
}
}
}
command
=
append
(
command
,
"--authorization-mode="
+
strings
.
Join
(
authzModes
,
","
))
command
=
append
(
command
,
"--authorization-mode="
+
strings
.
Join
(
authzModes
,
","
))
switch
authzMode
{
case
authzmodes
.
ModeABAC
:
command
=
append
(
command
,
"--authorization-policy-file="
+
kubeadmconstants
.
AuthorizationPolicyPath
)
case
authzmodes
.
ModeWebhook
:
command
=
append
(
command
,
"--authorization-webhook-config-file="
+
kubeadmconstants
.
AuthorizationWebhookConfigPath
)
}
return
command
return
command
}
}
...
...
cmd/kubeadm/app/master/manifests_test.go
View file @
68f69b2c
...
@@ -595,44 +595,53 @@ func TestGetSchedulerCommand(t *testing.T) {
...
@@ -595,44 +595,53 @@ func TestGetSchedulerCommand(t *testing.T) {
func
TestGetAuthzParameters
(
t
*
testing
.
T
)
{
func
TestGetAuthzParameters
(
t
*
testing
.
T
)
{
var
tests
=
[]
struct
{
var
tests
=
[]
struct
{
authMode
string
authMode
[]
string
expected
[]
string
expected
[]
string
}{
}{
{
{
authMode
:
""
,
authMode
:
[]
string
{}
,
expected
:
[]
string
{
expected
:
[]
string
{
"--authorization-mode=RBAC"
,
"--authorization-mode=RBAC"
,
},
},
},
},
{
{
authMode
:
"RBAC"
,
authMode
:
[]
string
{
"RBAC"
}
,
expected
:
[]
string
{
expected
:
[]
string
{
"--authorization-mode=RBAC"
,
"--authorization-mode=RBAC"
,
},
},
},
},
{
{
authMode
:
"AlwaysAllow"
,
authMode
:
[]
string
{
"AlwaysAllow"
}
,
expected
:
[]
string
{
expected
:
[]
string
{
"--authorization-mode=RBAC,AlwaysAllow"
,
"--authorization-mode=RBAC,AlwaysAllow"
,
},
},
},
},
{
{
authMode
:
"AlwaysDeny"
,
authMode
:
[]
string
{
"AlwaysDeny"
}
,
expected
:
[]
string
{
expected
:
[]
string
{
"--authorization-mode=RBAC,AlwaysDeny"
,
"--authorization-mode=RBAC,AlwaysDeny"
,
},
},
},
},
{
{
authMode
:
"ABAC"
,
authMode
:
[]
string
{
"ABAC"
}
,
expected
:
[]
string
{
expected
:
[]
string
{
"--authorization-mode=RBAC,ABAC"
,
"--authorization-mode=RBAC,ABAC"
,
"--authorization-policy-file=/etc/kubernetes/abac_policy.json"
,
"--authorization-policy-file=/etc/kubernetes/abac_policy.json"
,
},
},
},
},
{
{
authMode
:
"Webhook"
,
authMode
:
[]
string
{
"ABAC"
,
"Webhook"
}
,
expected
:
[]
string
{
expected
:
[]
string
{
"--authorization-mode=RBAC,Webhook"
,
"--authorization-mode=RBAC,ABAC,Webhook"
,
"--authorization-policy-file=/etc/kubernetes/abac_policy.json"
,
"--authorization-webhook-config-file=/etc/kubernetes/webhook_authz.conf"
,
},
},
{
authMode
:
[]
string
{
"ABAC"
,
"RBAC"
,
"Webhook"
},
expected
:
[]
string
{
"--authorization-mode=RBAC,ABAC,Webhook"
,
"--authorization-policy-file=/etc/kubernetes/abac_policy.json"
,
"--authorization-webhook-config-file=/etc/kubernetes/webhook_authz.conf"
,
"--authorization-webhook-config-file=/etc/kubernetes/webhook_authz.conf"
,
},
},
},
},
...
@@ -640,14 +649,10 @@ func TestGetAuthzParameters(t *testing.T) {
...
@@ -640,14 +649,10 @@ func TestGetAuthzParameters(t *testing.T) {
for
_
,
rt
:=
range
tests
{
for
_
,
rt
:=
range
tests
{
actual
:=
getAuthzParameters
(
rt
.
authMode
)
actual
:=
getAuthzParameters
(
rt
.
authMode
)
for
i
:=
range
actual
{
sort
.
Strings
(
actual
)
if
actual
[
i
]
!=
rt
.
expected
[
i
]
{
sort
.
Strings
(
rt
.
expected
)
t
.
Errorf
(
if
!
reflect
.
DeepEqual
(
actual
,
rt
.
expected
)
{
"failed getAuthzParameters:
\n\t
expected: %s
\n\t
actual: %s"
,
t
.
Errorf
(
"failed getAuthzParameters:
\n
expected:
\n
%v
\n
saw:
\n
%v"
,
rt
.
expected
,
actual
)
rt
.
expected
[
i
],
actual
[
i
],
)
}
}
}
}
}
}
}
...
...
cmd/kubeadm/app/preflight/checks.go
View file @
68f69b2c
...
@@ -520,11 +520,13 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error {
...
@@ -520,11 +520,13 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error {
}
}
// Check the config for authorization mode
// Check the config for authorization mode
switch
cfg
.
AuthorizationMode
{
for
_
,
authzMode
:=
range
cfg
.
AuthorizationModes
{
case
authzmodes
.
ModeABAC
:
switch
authzMode
{
checks
=
append
(
checks
,
FileExistingCheck
{
Path
:
kubeadmconstants
.
AuthorizationPolicyPath
})
case
authzmodes
.
ModeABAC
:
case
authzmodes
.
ModeWebhook
:
checks
=
append
(
checks
,
FileExistingCheck
{
Path
:
kubeadmconstants
.
AuthorizationPolicyPath
})
checks
=
append
(
checks
,
FileExistingCheck
{
Path
:
kubeadmconstants
.
AuthorizationWebhookConfigPath
})
case
authzmodes
.
ModeWebhook
:
checks
=
append
(
checks
,
FileExistingCheck
{
Path
:
kubeadmconstants
.
AuthorizationWebhookConfigPath
})
}
}
}
return
RunChecks
(
checks
,
os
.
Stderr
)
return
RunChecks
(
checks
,
os
.
Stderr
)
...
...
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