One more follow-up about this that I forgot to ask:
After server-side implements a default limit, if we request using limit=0 for example will we be able to retrieve all items at once?
You as well, have a great weekend and happy holidays! 🦆
One more follow-up about this that I forgot to ask:
After server-side implements a default limit, if we request using limit=0 for example will we be able to retrieve all items at once?
You as well, have a great weekend and happy holidays! 🦆
No worries! I was very excited to discover that you had just released SIN region!
Updated on my end: https://github.com/leee/netcup/issues/6
ZitatThe result is ordered by server name
Just to double check, I assume that this is also the case for other endpoints that have a `limit` parameter, not just servers?
Thank you, updated on my end: https://github.com/leee/netcup/issues/11
Hi [netcup] Sarah G. - just checking on the above ![]()
Also congratulations on the release of firewall! Looking forward to implementing that in tools soon.
Hi [netcup] Sarah G. - thank you for organizing the issues and reaching out to the relevant departments `:D` Looking forward to updates for them.
Regarding this issue: for clarification, how do CCP and SCP interact with each other here? Is the following guess correct?
Would it be possible to also document this somewhere, such as in the authentication part of the REST API documentation?
An additional question for clarity: are ID numbers in SCP REST API guaranteed to be unique for the duration of their life? In other words:
$ netcup auth print-access-token | jq -R 'split(".") | .[1] | @base64d | fromjson | .id'
"15...." (SCP user ID)
and
$ netcup servers list
ID NAME HOSTNAME NICKNAME DISABLED TEMPLATE
728538 v2202505273910341302 hostname.example.com nickname.example.com false 1041 (VPS 1000 ARM G11)
728540 v2202505273910341303 hostname.example.net nickname.example.net false 1041 (VPS 1000 ARM G11)
Are those ID numbers:
guaranteed to be the same and never change? Or should users of the REST APi always use `GET` methods to list items, find what they want, and reference the ID provided then? What would be the lifetime of that ID if it is considered temporary?
I also just want to mention these unanswered issues, but I assume you did not get a chance to review and notify the relevant department(s) yet:
Thank you, and have a great weekend! 🦆
Hi [netcup] Sarah G. - tonight I was going to work on implementing `netcup servers get ...` and `netcup servers set ...` but found another similar nullable OpenAPI spec violation:
$ curl -s -H "Authorization: Bearer $(go run cmd/netcup/main.go auth print-access-token)" \
"https://www.servercontrolpanel.de/scp-core/api/v1/servers/......" | jq '.serverLiveInfo.interfaces[0].vlanId'
null
This of course again is not deemed possible in your spec. All your endpoints should be reviewed and either the spec fixed or server behavior fixed.
Do you already have engineers assigned to work on the various REST API bug fixes? Would it be possible to get a soft timeline on the remediation efforts?
I will pause on my netcup CLI tool and OpenTofu (Terraform) provider work until we hear back on the state and timeline of further REST API development, and look forward to resuming when the API is actually ready for production use.
Thank you! 🦆 (I have seen the duck emoji used in many places in the forum, what does it stand for?
)
Thanks [netcup] Sarah G. ! I apologize for the many issues created in the REST API forum over the weekend, but I look forward to getting all those issues fixed so we can have a first class developer experience on netcup!
i suggest to send this to the support guys directly. this is a customer forum.
I understand this forum section is for feedback for the REST API which only recently exited beta (and possibly a little too early), and I assume [netcup] Sarah G. or another employee will triage issues into their infrastructure or developer experience team projects. If that is not the case, do let me know what the best venue for escalating structural issues, if it is something other than support tickets.
The IPv6 rDNS endpoints act strangely and frankly need a v2 rewrite.
Issue 1: addresses are treated as string literals and not normalized
As I understand things based on testing:
Example:
$ go run cmd/netcup/main.go rdns get 2a0a:4cc0:2000:8fe5::
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:cafe:: test-0.example.com
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:cafe::0 test-1.example.com
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:cafe::00 test-2.example.com
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:cafe::000 test-3.example.com
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:cafe::0000 test-4.example.com
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:f00d::0000 test-4.example.net
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:f00d::000 test-3.example.net
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:f00d::00 test-2.example.net
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:f00d::0 test-1.example.net
$ go run cmd/netcup/main.go rdns set 2a0a:4cc0:2000:8fe5:f00d:: test-0.example.net
Alles anzeigen
These are the same addresses. Their most compact and expanded forms are:
2a0a:4cc0:2000:8fe5:cafe:: / 2a0a:4cc0:2000:8fe5:cafe:0000:0000:0000
2a0a:4cc0:2000:8fe5:f00d:: / 2a0a:4cc0:2000:8fe5:f00d:0000:0000:0000
The expectation then is we should see cafe be test-4.example.com, and f00d be test-0.example.net. And sure enough, after waiting for the clock to go past a multiple of 10 minutes (and a few seconds), we check what is actually being served from DNS:
$ dig @root-dns.netcup.net +noall +answer -x 2a0a:4cc0:2000:8fe5:cafe::
0.0.0.0.0.0.0.0.0.0.0.0.e.f.a.c.5.e.f.8.0.0.0.2.0.c.c.4.a.0.a.2.ip6.arpa. 86400 IN PTR test-4.example.com.
$ dig @root-dns.netcup.net +noall +answer -x 2a0a:4cc0:2000:8fe5:f00d::
0.0.0.0.0.0.0.0.0.0.0.0.d.0.0.f.5.e.f.8.0.0.0.2.0.c.c.4.a.0.a.2.ip6.arpa. 86400 IN PTR test-0.example.net.
As you can see, the responses show that the most recent POST is what gets committed to DNS configuration. This is perfectly fine and expected.
What's not acceptable is this API response:
$ go run cmd/netcup/main.go rdns get 2a0a:4cc0:2000:8fe5::
2a0a:4cc0:2000:8fe5:cafe:: test-0.example.com
2a0a:4cc0:2000:8fe5:cafe::0 test-1.example.com
2a0a:4cc0:2000:8fe5:cafe::00 test-2.example.com
2a0a:4cc0:2000:8fe5:cafe::000 test-3.example.com
2a0a:4cc0:2000:8fe5:cafe::0000 test-4.example.com
2a0a:4cc0:2000:8fe5:f00d:: test-0.example.net
2a0a:4cc0:2000:8fe5:f00d::0 test-1.example.net
2a0a:4cc0:2000:8fe5:f00d::00 test-2.example.net
2a0a:4cc0:2000:8fe5:f00d::000 test-3.example.net
2a0a:4cc0:2000:8fe5:f00d::0000 test-4.example.net
Alles anzeigen
Like we state before, all these addresses are the same. While it is possible (but typically recommended) to have multiple PTR records for the same address, that is not what is going on here. There should only be 1 instance of each address, even if in the future they can be for multiple PTR records. It doesn't matter if you store records compact or expanded, that mechanism needs to be consistent to avoid ambiguities like the above.
Issue 2: IPv6 GET endpoint is not for a singular address, but for a subnet prefix (with an undisclosed /64 prefix mask)
Related to the above issue, when I `rdns get 2a0a:4cc0:2000:8fe5::` I expect to get the PTR record(s) for the address `2a0a:4cc0:2000:8fe5:0000:0000:0000:0000`. Instead, what we get is a response that we would've expected from a command like `rdns list` or `rdns list 2a0a:4cc0:2000:8fe5::/64`.
At minimum, this behavior should be documented. However, I believe these endpoints should be written to be consistent, so like the IPv4 GET /api/v1/rdns/ipv4/{ip} endpoint, the IPv6 GET /api/v1/rdns/ipv6/{ip} endpoint is for a single address.
Issue 3: IPv4 and IPv6 endpoints are not consistent with each other
Similarly to the above issue:
Issue 4: deleting an IPv6 PTR record sets it to `nobody.yourvserver.net` and not back to nothing
Similar to what I mentioned in another post regarding IPv4.
IPv6 addresses start out without any PTR records. Then the user adds one. The user then deletes that record, causing the address to have a `nobody.yourvserver.net` PTR record just like IPv4.
This is inconsistent behavior. Addresses that no longer have any PTR records in the REST API should stay that way.
$ dig @root-dns.netcup.net +short -x 198.51.100.100
v22025example.nicesrv.de.
$ go run cmd/netcup/main.go rdns get 198.51.100.100
v22025example.nicesrv.de
$ go run cmd/netcup/main.go rdns delete 198.51.100.100
$ curl -s -H "Accept: application/hal+json" \
-H "Authorization: Bearer $(go run cmd/netcup/main.go auth print-access-token)" \
-X GET \
'https://servercontrolpanel.de/scp-core/api/v1/rdns/ipv4/198.51.100.100' | jq
{
"rdns": null
}
Alles anzeigen
After using `DELETE /api/v1/rdns/ipv4/{ip}` on a vServer IP with the REST API, I would expect for there to be no PTR record. However:
$ # wait 10 minutes for netcup infrastructure to deploy changed records
$ dig @root-dns.netcup.net +short -x 198.51.100.100
nobody.yourvserver.net.
If netcup would like `nobody.yourvserver.net` to be a default PTR record for servers that have no explicitly defined hostname, the REST API should also report this on `GET`.
It is also especially weird that SCP does not have the updated hostname either. Is that also on a batched job like the every 10 minute DNS server updates but at a less frequent interval?
I'm guessing there will likely be other endpoints that violate spec like this, and the API should be reviewed top to bottom.
I'm trying to implement rdns routines:
$ go run cmd/netcup/main.go rdns delete ...
$ go run cmd/netcup/main.go rdns get ...
Error: decode response: decode application/hal+json: decode RdnsIpv4: callback: decode field "rdns": unexpected byte 110 'n' at 8
exit status 1
When I look at the actual response directly:
$ curl -s -H "Accept: application/hal+json" \
-H "Authorization: Bearer $(go run cmd/netcup/main.go auth print-access-token)" \
-X 'GET' \
'https://servercontrolpanel.de/scp-core/api/v1/rdns/ipv4/...' | jq
{
"rdns": null
}
This is a problem because the OpenAPI spec does not say this is possible.
$ jq '.components.schemas.RdnsIpv4' openapi.json
{
"type": "object",
"properties": {
"rdns": {
"type": "string"
}
}
}
The spec is missing a nullable which you can read about here: https://swagger.io/docs/specifica…ata-types/#null
Because the string data type is missing a nullable attribute, OpenAPI generator tools, like ogen in my case, generates:
Which only allows this field to be a string or absent, but not null.
I'd like to gain clarity on default query limit behavior. For example, I'm currently playing with the `GET /api/v1/servers` endpoint.
The OpenAPI spec defines a `limit` query param with minimum: 0, but doesn't specify a default value. I only have a few servers, so:
It would be helpful if these defaults/limits could be documented in the OpenAPI spec (via default and maximum in the schema).
The API documentation only lists the `/revoke` endpoint, which only revokes a specific JWT. The OIDC realm also makes available the `/logout` endpoint, which allows revoking a session entirely.
However, being able to revoke a specific token, or logout a current session, requires having said tokens. If the user discards/forgets these tokens, but believes them to be compromised or simply wishes to revoke/logout unused sessions, they can only wait 30 days of disuse before those refresh tokens fall off.
Users should be allowed to review their active sessions and log them out. Since this is Keycloak, I believe the appropriate areas that we can not access that we should are:
This is especially important as I just discovered that when I change my SCP password, previous refresh tokens still remain active. Lets say I were compromised by an attacker who takes control of my netcup resources through the REST API. Even if I change my password, I would still be compromised.
Along with access to session management, users should be able to revoke all sessions at once as an optional setting during a password change.
I am able to generate perpetual refresh tokens using legacy username/password authentication and without use of any 2FA:
$ curl -s -X POST 'https://servercontrolpanel.de/realms/scp/protocol/openid-connect/token' \
-d 'client_id=scp' \
-d 'grant_type=password' \
-d 'username=<customer number>' \
-d 'password=<number>' \
-d 'scope=offline_access openid' | jq
{
"access_token": "...",
"expires_in": 300,
"refresh_expires_in": 0,
"refresh_token": "...",
"token_type": "Bearer",
"id_token": "...",
"not-before-policy": 0,
"session_state": "...",
"scope": "openid profile offline_access email"
}
Alles anzeigen
OAuth 2.1 does not have this grant type, and OAuth 2.0 Security BCP (Best Current Practice) disallows password grants:
2.4. Resource Owner Password Credentials Grant
The resource owner password credentials grant [RFC6749] MUST NOT be used. This grant type insecurely exposes the credentials of the resource owner to the client. Even if the client is benign, usage of this grant results in an increased attack surface (i.e., credentials can leak in more places than just the authorization server) and in training users to enter their credentials in places other than the authorization server.
Furthermore, the resource owner password credentials grant is not designed to work with two-factor authentication and authentication processes that require multiple user interaction steps. Authentication with cryptographic credentials (cf. WebCrypto [W3C.WebCrypto], WebAuthn [W3C.WebAuthn]) may be impossible to implement with this grant type, as it is usually bound to a specific web origin.
In addition, while reviewing the OIDC configuration, there were several other "MUST" violations as well as some other settings that should follow the "SHOULD" recommendations. It may be worth performing a compliance review of your configuration against the security BCP. Issues that I immediately see:
In the documentation for authentication, netcup states:
Zitat
The documentation wording makes me believe netcup's intent is that the original refresh token continues to be valid in perpetuity as long as it is used at least once every 30 days. That should mean it is not necessary to issue a new refresh token in the response for this endpoint. See:
The response to the refresh token grant is the same as when issuing an access token. You can optionally issue a new refresh token in the response, or if you don’t include a new refresh token, the client assumes the current refresh token will continue to be valid.
and
(H) The authorization server authenticates the client and validates
the refresh token, and if valid, issues a new access token (and,
optionally, a new refresh token).
However, this endpoint provides a response that includes the following key-values, including a new `refresh_token` which does not match behavior expected by documentation:
$ curl -s 'https://servercontrolpanel.de/realms/scp/protocol/openid-connect/token' \
-d 'client_id=scp'\
-d 'refresh_token=<refresh jwt 1>' \
-d 'grant_type=refresh_token' | jq
{
"access_token": "...",
"expires_in": 300,
"refresh_expires_in": 0,
"refresh_token": "???? ???? refresh jwt 2 ???? ????",
"token_type": "Bearer",
"id_token": "..."
"not-before-policy": 0,
"session_state": "...",
"scope": "openid profile offline_access email"
}
Alles anzeigen
In einem ersten Schritt haben wir nun die Version des SCP in die OpenAPI Spec eingefügt.
Die Version wird bei jedem SCP Release angepasst, unabhängig davon, ob es eine Änderung an der API gegeben hat oder nicht.
Nach der Beta-Phase wird es keine breaking changes mehr an der API gegeben. Bei erforderlichen breaking changes erfolgt die Versionierung einzelner Endpoints über den Pfad.
Das Thema Changelog haben wir weiterhin am Radar.
Please include this important lifecycle management information somewhere in the Swagger documentation, such as in the description field:
openapi: 3.0
info: title: SCP (Server Control Panel) REST API version: 2025.1124.124251
description: |-
Die Version wird bei jedem SCP Release angepasst, unabhängig davon, ob es eine Änderung an der API gegeben hat oder nicht.
The version is adjusted with each SCP release, regardless of whether there has been a change to the API or not.
Bei erforderlichen breaking changes erfolgt die Versionierung einzelner Endpoints über den Pfad.
If breaking changes are necessary, individual endpoints will be versioned via the path.
CHANGELOG: ...
externalDocs:
description: netcup Forums: SCP (Server Control Panel) - REST API
url: https://forum.netcup.de/netcup-applications/scp-server-control-panel/scp-server-control-panel-rest-api/
Alles anzeigen
Prior to implementation of this new REST API using Keycloak for IAM, the only identity customers needed to be aware of was their netcup customer number, used in CCP/SCP.
However, with Keycloak, we now have to be aware that the customer number is being used as a `username` and that there is a separate `id` unrelated to the customer number.
If possible, the customer number/username should stay in lockstep with id. And for the time being, this distinction should be clarified in documentation.
Even though I have 2FA enabled in Customer Control Panel (CCP), Keycloak only asks for my username (customer number) and password.
I have forgotten that SCP is a separate entity with its own authentication management, and does not have 2FA at all.